*** ./doc/src/sgml/func.sgml.orig 2010-09-09 02:48:22.000000000 +0200 --- ./doc/src/sgml/func.sgml 2010-09-25 21:22:01.493460314 +0200 *************** *** 10386,10391 **** --- 10386,10411 ---- + + Arithmetic median + median + + median(expression) + + + smallint, int, + bigint, real, double + precision, or numeric + + + double precision for floating-point arguments, + otherwise numeric + + arithmetic median + + + + regr_avgx(Y, X) *** ./src/backend/utils/adt/median.c.orig 2010-09-25 22:23:01.000000000 +0200 --- ./src/backend/utils/adt/median.c 2010-09-26 12:16:40.798602994 +0200 *************** *** 0 **** --- 1,649 ---- + #include "postgres.h" + + #include "fmgr.h" + #include "miscadmin.h" + #include + + #include "access/nbtree.h" + #include "catalog/pg_type.h" + #include "catalog/pg_index.h" + #include "parser/parse_coerce.h" + #include "parser/parse_oper.h" + #include "utils/builtins.h" + #include "utils/datum.h" + #include "utils/lsyscache.h" + #include "utils/memutils.h" + #include "utils/numeric.h" + #include "utils/tuplesort.h" + + /* + * just memory storage - store a Datum to memory. Allows repetetive + * sort and append a new value. + */ + typedef struct + { + void *tuple; + Datum datum1; + bool isnull1; + } SortTuple; + + /* + * Simple sort storage - used just only Datums. + */ + typedef struct + { + long allowedMem; + long availMem; + MemoryContext sortcontext; + + SortTuple *memtuples; + int memtupcount; + int memtupsize; + int current; + + Oid datumType; + FmgrInfo sortOpFn; + int sortFnFlags; + int datumTypLen; + bool datumTypByVal; + } Memsortstate; + + /* + * used as type of state variable median's function. It uses a + * tuplesort as safe and inteligent storage. But we cannot to use + * a tuplesort under window aggregate context. Then we have to use a + * array of Datum. The API is similar to tuplesort API. + */ + typedef struct + { + int nelems; /* number of valid entries */ + bool use_tuplesort; + Tuplesortstate *sortstate; + Memsortstate *memsortstate; + FmgrInfo cast_func_finfo; + bool datumTypByVal; + } MedianAggState; + + #define USEMEM(state, amt) ((state)->availMem -= (amt)) + #define LACKMEM(state) ((state)->availMem < 0) + #define FREEMEM(state, amt) ((state)->availMem += (amt)) + + static int compare_datum(const SortTuple *a, const SortTuple *b, Memsortstate *state); + + + static Memsortstate * + memsort_begin_datum(Oid datumType, + Oid sortOperator, bool nullsFirstFlag, + int workMem) + { + MemoryContext sortcontext; + MemoryContext oldcontext; + bool reverse; + Oid sortFunction; + Memsortstate *state; + int16 typlen; + bool typbyval; + + /* + * Create a working memory context for this sort operation. All + * data needed by the sort will live iside this context. + */ + sortcontext = AllocSetContextCreate(CurrentMemoryContext, + "MemorySort", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + oldcontext = MemoryContextSwitchTo(sortcontext); + state = (Memsortstate *) palloc0(sizeof(Memsortstate)); + + state->allowedMem = workMem * 1024L; + state->availMem = state->allowedMem; + state->memtupcount = 0; + state->memtupsize = 1024; + state->memtuples = (SortTuple*) palloc(state->memtupsize * sizeof(SortTuple)); + state->sortcontext = sortcontext; + + USEMEM(state, GetMemoryChunkSpace(state->memtuples)); + + if (LACKMEM(state)) + elog(ERROR, "insufficient memory allowed for sort"); + + state->datumType = datumType; + if (!get_compare_function_for_ordering_op(sortOperator, &sortFunction, &reverse)) + elog(ERROR, "operator %u is not a valid ordering operator", + sortOperator); + fmgr_info(sortFunction, &state->sortOpFn); + + /* set ordering flags */ + state->sortFnFlags = reverse ? SK_BT_DESC : 0; + if (nullsFirstFlag) + state->sortFnFlags |= SK_BT_NULLS_FIRST; + + /* lookup necessary attributies of the datum type */ + get_typlenbyval(datumType, &typlen, &typbyval); + state->datumTypLen = typlen; + state->datumTypByVal = typbyval; + + MemoryContextSwitchTo(oldcontext); + + return state; + } + + static void + freeDatum(Datum value, bool typByVal, bool isNull) + { + if (!typByVal && !isNull) + { + Pointer s = DatumGetPointer(value); + + pfree(s); + } + } + + /* + * append a datum + */ + static void + memsort_putdatum(Memsortstate *state, Datum val, bool isNull) + { + MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext); + SortTuple stup; + + if (isNull || state->datumTypByVal) + { + stup.datum1 = val; + stup.isnull1 = isNull; + stup.tuple = NULL; + } + else + { + stup.datum1 = datumCopy(val, state->datumTypByVal, state->datumTypLen); + stup.isnull1 = false; + stup.tuple = DatumGetPointer(stup.datum1); + USEMEM(state, GetMemoryChunkSpace(stup.tuple)); + } + + if (state->memtupcount == state->memtupsize) + { + if (state->availMem <= (long) (state->memtupsize * sizeof(SortTuple))) + elog(ERROR, "unexpected out-of-memory situation during sort"); + if ((Size) (state->memtupsize * 2) >= MaxAllocSize / sizeof(SortTuple)) + elog(ERROR, "unexpected out-of-memory situation during sort"); + + FREEMEM(state, GetMemoryChunkSpace(state->memtuples)); + state->memtupsize *= 2; + state->memtuples = (SortTuple *) + repalloc(state->memtuples, state->memtupsize * sizeof(SortTuple)); + USEMEM(state, GetMemoryChunkSpace(state->memtuples)); + if (LACKMEM(state)) + elog(ERROR, "unexpected out-of-memory situation during sort"); + } + + state->memtuples[state->memtupcount++] = stup; + MemoryContextSwitchTo(oldcontext); + } + + /* + * sort a current stored tuples + */ + static void + memsort_performsort(Memsortstate *state) + { + if (state->memtupcount > 1) + qsort_arg((void *) state->memtuples, + state->memtupcount, + sizeof(SortTuple), + (qsort_arg_comparator) compare_datum, + (void *) state); + + state->current = 0; + } + + static bool + memsort_getdatum(Memsortstate *state, + Datum *val, bool *isNull) + { + SortTuple *stup; + MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext); + + if (state->current < state->memtupcount) + { + stup = &state->memtuples[state->current++]; + if (stup->isnull1 || state->datumTypByVal) + { + *val = stup->datum1; + *isNull = stup->isnull1; + } + else + { + *val = datumCopy(stup->datum1, false, state->datumTypLen); + *isNull = false; + } + + MemoryContextSwitchTo(oldcontext); + + return true; + } + + MemoryContextSwitchTo(oldcontext); + + return false; + } + + /* + * Inline-able copy of FunctionCall2() to save some cycles in sorting. + */ + static inline Datum + myFunctionCall2(FmgrInfo *flinfo, Datum arg1, Datum arg2) + { + FunctionCallInfoData fcinfo; + Datum result; + + InitFunctionCallInfoData(fcinfo, flinfo, 2, NULL, NULL); + + fcinfo.arg[0] = arg1; + fcinfo.arg[1] = arg2; + fcinfo.argnull[0] = false; + fcinfo.argnull[1] = false; + + result = FunctionCallInvoke(&fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo.isnull) + elog(ERROR, "function %u returned NULL", fcinfo.flinfo->fn_oid); + + return result; + } + + /* + * Apply a sort function (by now converted to fmgr lookup form) + * and return a 3-way comparison result. This takes care of handling + * reverse-sort and NULLs-ordering properly. We assume that DESC and + * NULLS_FIRST options are encoded in sk_flags the same way btree does it. + */ + static inline int32 + inlineApplySortFunction(FmgrInfo *sortFunction, int sk_flags, + Datum datum1, bool isNull1, + Datum datum2, bool isNull2) + { + int32 compare; + + if (isNull1) + { + if (isNull2) + compare = 0; /* NULL "=" NULL */ + else if (sk_flags & SK_BT_NULLS_FIRST) + compare = -1; /* NULL "<" NOT_NULL */ + else + compare = 1; /* NULL ">" NOT_NULL */ + } + else if (isNull2) + { + if (sk_flags & SK_BT_NULLS_FIRST) + compare = 1; /* NOT_NULL ">" NULL */ + else + compare = -1; /* NOT_NULL "<" NULL */ + } + else + { + compare = DatumGetInt32(myFunctionCall2(sortFunction, + datum1, datum2)); + + if (sk_flags & SK_BT_DESC) + compare = -compare; + } + + return compare; + } + + static int + compare_datum(const SortTuple *a, const SortTuple *b, Memsortstate *state) + { + /* Allow interrupting long sorts */ + CHECK_FOR_INTERRUPTS(); + + return inlineApplySortFunction(&state->sortOpFn, state->sortFnFlags, + a->datum1, a->isnull1, + b->datum1, b->isnull1); + } + + static MedianAggState * + makeMedianAggState(FunctionCallInfo fcinfo, Oid valtype, Oid targettype) + { + MemoryContext oldctx; + MemoryContext aggcontext; + MedianAggState *aggstate; + Oid sortop, + castfunc; + CoercionPathType pathtype; + int16 typlen; + bool typbyval; + + if (!AggCheckCallContext(fcinfo, &aggcontext)) + { + /* cannot be called directly because of internal-type argument */ + elog(ERROR, "median_transfn called in non-aggregate context"); + } + + oldctx = MemoryContextSwitchTo(aggcontext); + + aggstate = (MedianAggState *) palloc0(sizeof(MedianAggState)); + + valtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + get_sort_group_operators(valtype, + true, false, false, + &sortop, NULL, NULL); + + /* lookup necessary attributies of the datum type, used for datumFree */ + get_typlenbyval(valtype, &typlen, &typbyval); + aggstate->datumTypByVal = typbyval; + + /* + * use a Datum mem sort insted a Tuplesort under windows aggregates + */ + if (fcinfo->context && IsA(fcinfo->context, WindowAggState)) + { + aggstate->memsortstate = memsort_begin_datum(valtype, + sortop, + SORTBY_NULLS_DEFAULT, + work_mem); + aggstate->use_tuplesort = false; + } + else + { + aggstate->sortstate = tuplesort_begin_datum(valtype, + sortop, + SORTBY_NULLS_DEFAULT, + work_mem, true); + aggstate->use_tuplesort = true; + } + + MemoryContextSwitchTo(oldctx); + + if (valtype != targettype) + { + /* find a cast function */ + pathtype = find_coercion_pathway(targettype, valtype, + COERCION_EXPLICIT, + &castfunc); + if (pathtype == COERCION_PATH_FUNC) + { + Assert(OidIsValid(castfunc)); + fmgr_info_cxt(castfunc, &aggstate->cast_func_finfo, + aggcontext); + } + else if (pathtype == COERCION_PATH_RELABELTYPE) + { + aggstate->cast_func_finfo.fn_oid = InvalidOid; + } + else + elog(ERROR, "no conversion function from %s %s", + format_type_be(valtype), + format_type_be(targettype)); + } + + return aggstate; + } + + static void + putdatum(MedianAggState *aggstate, Datum val) + { + if (aggstate->use_tuplesort) + { + Assert(aggstate->sortstate != NULL); + tuplesort_putdatum(aggstate->sortstate, val, false); + } + else + { + Assert(aggstate->memsortstate != NULL); + memsort_putdatum(aggstate->memsortstate, val, false); + } + } + + static void + performsort(MedianAggState *aggstate) + { + if (aggstate->use_tuplesort) + { + Assert(aggstate->sortstate != NULL); + tuplesort_performsort(aggstate->sortstate); + } + else + { + Assert(aggstate->memsortstate != NULL); + memsort_performsort(aggstate->memsortstate); + } + } + + static bool + getdatum(MedianAggState *aggstate, + Datum *value, bool *isNull) + { + if (aggstate->use_tuplesort) + { + Assert(aggstate->sortstate != NULL); + return tuplesort_getdatum(aggstate->sortstate, + true, + value, isNull); + } + else + { + Assert(aggstate->memsortstate != NULL); + return memsort_getdatum(aggstate->memsortstate, + value, isNull); + } + } + + /* + * append a non NULL value to tuplesort + */ + static Datum + common_median_transfn(FunctionCallInfo fcinfo, Oid typoid, Oid targetoid) + { + MedianAggState *aggstate; + + aggstate = PG_ARGISNULL(0) ? NULL : (MedianAggState *) PG_GETARG_POINTER(0); + + if (!PG_ARGISNULL(1)) + { + if (aggstate == NULL) + aggstate = makeMedianAggState(fcinfo, typoid, targetoid); + + putdatum(aggstate, PG_GETARG_DATUM(1)); + aggstate->nelems++; + } + + PG_RETURN_POINTER(aggstate); + } + + /* + * just wrappers to be opr sanity checks happy + */ + Datum + median_numeric_transfn(PG_FUNCTION_ARGS) + { + return common_median_transfn(fcinfo, + NUMERICOID, NUMERICOID); + } + + Datum + median_int8_transfn(PG_FUNCTION_ARGS) + { + return common_median_transfn(fcinfo, + INT8OID, NUMERICOID); + } + + Datum + median_int4_transfn(PG_FUNCTION_ARGS) + { + return common_median_transfn(fcinfo, + INT4OID, NUMERICOID); + } + + Datum + median_int2_transfn(PG_FUNCTION_ARGS) + { + return common_median_transfn(fcinfo, + INT2OID, NUMERICOID); + } + + Datum + median_double_transfn(PG_FUNCTION_ARGS) + { + return common_median_transfn(fcinfo, + FLOAT8OID, FLOAT8OID); + } + + Datum + median_float_transfn(PG_FUNCTION_ARGS) + { + return common_median_transfn(fcinfo, + FLOAT4OID, FLOAT8OID); + } + + /* + * Used for reading values from tuplesort. The value has to be + * double or cast function is defined (and used). + */ + static double + to_double(Datum value, FmgrInfo *cast_func_finfo) + { + /* when valtype is same as target type, returns directly */ + if (cast_func_finfo->fn_oid == InvalidOid) + return DatumGetFloat8(value); + + return DatumGetFloat8(FunctionCall1(cast_func_finfo, value)); + } + + /* + * Used as final function for median when result is double. + */ + Datum + median_finalfn_double(PG_FUNCTION_ARGS) + { + MedianAggState *aggstate; + + Assert(AggCheckCallContext(fcinfo, NULL)); + + aggstate = PG_ARGISNULL(0) ? NULL : (MedianAggState *) PG_GETARG_POINTER(0); + + if (aggstate != NULL) + { + int lidx; + int hidx; + Datum value; + bool isNull; + int i = 1; + double result = 0; + + hidx = aggstate->nelems / 2 + 1; + lidx = (aggstate->nelems + 1) / 2; + + performsort(aggstate); + + while (getdatum(aggstate, &value, &isNull)) + { + if (i++ == lidx) + { + result = to_double(value, &aggstate->cast_func_finfo); + freeDatum(value, aggstate->datumTypByVal, isNull); + + if (lidx != hidx) + { + getdatum(aggstate, &value, &isNull); + result = (result + to_double(value, &aggstate->cast_func_finfo)) / 2.0; + freeDatum(value, aggstate->datumTypByVal, isNull); + } + break; + } + } + + /* only for tuplesort call a end function */ + if (aggstate->use_tuplesort) + tuplesort_end(aggstate->sortstate); + + PG_RETURN_FLOAT8(result); + } + else + PG_RETURN_NULL(); + } + + /* + * Used for reading values from tuplesort. The value has to be + * Numeric or cast function is defined (and used). + */ + static Numeric + to_numeric(Datum value, FmgrInfo *cast_func_finfo) + { + /* when valtype is same as target type, returns directly */ + if (cast_func_finfo->fn_oid == InvalidOid) + return DatumGetNumeric(value); + + return DatumGetNumeric(FunctionCall1(cast_func_finfo, value)); + } + + /* + * Used as final function for median when result is numeric. + */ + Datum + median_finalfn_numeric(PG_FUNCTION_ARGS) + { + MedianAggState *aggstate; + + Assert(AggCheckCallContext(fcinfo, NULL)); + + aggstate = PG_ARGISNULL(0) ? NULL : (MedianAggState *) PG_GETARG_POINTER(0); + + if (aggstate != NULL) + { + int lidx; + int hidx; + Datum a_value; + bool a_isNull; + int i = 1; + Numeric result = NULL; /* be compiler quiet */ + + hidx = aggstate->nelems / 2 + 1; + lidx = (aggstate->nelems + 1) / 2; + + performsort(aggstate); + + while (getdatum(aggstate, &a_value, &a_isNull)) + { + if (i++ == lidx) + { + result = to_numeric(a_value, &aggstate->cast_func_finfo); + + if (lidx != hidx) + { + Datum b_value; + bool b_isNull; + Numeric stack; + + getdatum(aggstate, &b_value, &b_isNull); + stack = to_numeric(b_value, &aggstate->cast_func_finfo); + + stack = DatumGetNumeric(DirectFunctionCall2(numeric_add, + NumericGetDatum(stack), + NumericGetDatum(result))); + result = DatumGetNumeric(DirectFunctionCall2(numeric_div, + NumericGetDatum(stack), + DirectFunctionCall1(float4_numeric, + Float4GetDatum(2.0)))); + freeDatum(b_value, aggstate->datumTypByVal, b_isNull); + } + break; + } + else + freeDatum(a_value, aggstate->datumTypByVal, a_isNull); + } + + /* only for tuplesort call a end function */ + if (aggstate->use_tuplesort) + tuplesort_end(aggstate->sortstate); + + PG_RETURN_NUMERIC(result); + } + else + PG_RETURN_NULL(); + } *** ./src/include/catalog/pg_aggregate.h.orig 2010-09-25 21:21:11.081451603 +0200 --- ./src/include/catalog/pg_aggregate.h 2010-09-25 21:22:01.497461866 +0200 *************** *** 226,231 **** --- 226,239 ---- /* text */ DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 _null_ )); + /* median */ + DATA(insert ( 3123 median_double_transfn median_finalfn_double 0 2281 _null_ )); + DATA(insert ( 3124 median_float_transfn median_finalfn_double 0 2281 _null_ )); + DATA(insert ( 3125 median_numeric_transfn median_finalfn_numeric 0 2281 _null_ )); + DATA(insert ( 3126 median_int8_transfn median_finalfn_numeric 0 2281 _null_ )); + DATA(insert ( 3127 median_int4_transfn median_finalfn_numeric 0 2281 _null_ )); + DATA(insert ( 3128 median_int2_transfn median_finalfn_numeric 0 2281 _null_ )); + /* * prototypes for functions in pg_aggregate.c */ *** ./src/include/catalog/pg_proc.h.orig 2010-09-03 03:34:55.000000000 +0200 --- ./src/include/catalog/pg_proc.h 2010-09-25 21:22:01.500452259 +0200 *************** *** 4850,4855 **** --- 4850,4884 ---- DATA(insert OID = 3114 ( nth_value PGNSP PGUID 12 1 0 0 f t f t f i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_nth_value _null_ _null_ _null_ )); DESCR("fetch the Nth row value"); + /* median aggregate */ + DATA(insert OID = 3115 ( median_numeric_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 1700" _null_ _null_ _null_ _null_ median_numeric_transfn _null_ _null_ _null_ )); + DESCR("median transident function for numeric"); + DATA(insert OID = 3116 ( median_int8_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 20" _null_ _null_ _null_ _null_ median_int8_transfn _null_ _null_ _null_ )); + DESCR("median transident function for bigint"); + DATA(insert OID = 3117 ( median_int4_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 23" _null_ _null_ _null_ _null_ median_int4_transfn _null_ _null_ _null_ )); + DESCR("median transident function for int"); + DATA(insert OID = 3118 ( median_int2_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 21" _null_ _null_ _null_ _null_ median_int2_transfn _null_ _null_ _null_ )); + DESCR("median transident function for smallint"); + DATA(insert OID = 3119 ( median_double_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 701" _null_ _null_ _null_ _null_ median_double_transfn _null_ _null_ _null_ )); + DESCR("median transident function for double precision"); + DATA(insert OID = 3120 ( median_float_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 700" _null_ _null_ _null_ _null_ median_float_transfn _null_ _null_ _null_ )); + DESCR("median transident function for real"); + DATA(insert OID = 3121 ( median_finalfn_double PGNSP PGUID 12 1 0 0 f f f f f i 1 0 701 "2281" _null_ _null_ _null_ _null_ median_finalfn_double _null_ _null_ _null_ )); + DESCR("median final function with double precision result"); + DATA(insert OID = 3122 ( median_finalfn_numeric PGNSP PGUID 12 1 0 0 f f f f f i 1 0 1700 "2281" _null_ _null_ _null_ _null_ median_finalfn_numeric _null_ _null_ _null_ )); + DESCR("median final function with numeric result"); + DATA(insert OID = 3123 ( median PGNSP PGUID 12 1 0 0 t f f f f i 1 0 701 "701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("median aggregate"); + DATA(insert OID = 3124 ( median PGNSP PGUID 12 1 0 0 t f f f f i 1 0 701 "700" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("median aggregate"); + DATA(insert OID = 3125 ( median PGNSP PGUID 12 1 0 0 t f f f f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("median aggregate"); + DATA(insert OID = 3126 ( median PGNSP PGUID 12 1 0 0 t f f f f i 1 0 1700 "20" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("median aggregate"); + DATA(insert OID = 3127 ( median PGNSP PGUID 12 1 0 0 t f f f f i 1 0 1700 "23" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("median aggregate"); + DATA(insert OID = 3128 ( median PGNSP PGUID 12 1 0 0 t f f f f i 1 0 1700 "21" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("median aggregate"); /* * Symbolic values for provolatile column: these indicate whether the result *** ./src/include/utils/builtins.h.orig 2010-09-03 03:34:55.000000000 +0200 --- ./src/include/utils/builtins.h 2010-09-25 21:22:01.530706224 +0200 *************** *** 934,939 **** --- 934,947 ---- extern Datum int8_avg(PG_FUNCTION_ARGS); extern Datum width_bucket_numeric(PG_FUNCTION_ARGS); extern Datum hash_numeric(PG_FUNCTION_ARGS); + extern Datum median_numeric_transfn(PG_FUNCTION_ARGS); + extern Datum median_int8_transfn(PG_FUNCTION_ARGS); + extern Datum median_int4_transfn(PG_FUNCTION_ARGS); + extern Datum median_int2_transfn(PG_FUNCTION_ARGS); + extern Datum median_double_transfn(PG_FUNCTION_ARGS); + extern Datum median_float_transfn(PG_FUNCTION_ARGS); + extern Datum median_finalfn_double(PG_FUNCTION_ARGS); + extern Datum median_finalfn_numeric(PG_FUNCTION_ARGS); /* ri_triggers.c */ extern Datum RI_FKey_check_ins(PG_FUNCTION_ARGS); *** ./src/test/regress/expected/numeric.out.orig 2010-09-25 21:21:11.086476751 +0200 --- ./src/test/regress/expected/numeric.out 2010-09-26 12:27:45.000000000 +0200 *************** *** 1372,1374 **** --- 1372,1423 ---- 12345678901234567890 (1 row) + -- median test + create table median_test (a int, b bigint, c smallint, d numeric, e double precision, f real); + insert into median_test select i,i,i,i,i,i from generate_series(1,10) g(i); + select median(a), + median(b), + median(c), + median(d), + median(e), + median(f) from median_test; + median | median | median | median | median | median + --------------------+--------------------+--------------------+--------------------+--------+-------- + 5.5000000000000000 | 5.5000000000000000 | 5.5000000000000000 | 5.5000000000000000 | 5.5 | 5.5 + (1 row) + + truncate table median_test; + insert into median_test select i,i,i,i,i,i from generate_series(1,11) g(i); + select median(a), + median(b), + median(c), + median(d), + median(e), + median(f) from median_test; + median | median | median | median | median | median + --------+--------+--------+--------+--------+-------- + 6 | 6 | 6 | 6 | 6 | 6 + (1 row) + + select median(a) over (order by a), + median(b) over (order by a), + median(c) over (order by a), + median(d) over (order by a), + median(e) over (order by a), + median(f) over (order by a) from median_test; + median | median | median | median | median | median + --------------------+--------------------+--------------------+--------------------+--------+-------- + 1 | 1 | 1 | 1 | 1 | 1 + 1.5000000000000000 | 1.5000000000000000 | 1.5000000000000000 | 1.5000000000000000 | 1.5 | 1.5 + 2 | 2 | 2 | 2 | 2 | 2 + 2.5000000000000000 | 2.5000000000000000 | 2.5000000000000000 | 2.5000000000000000 | 2.5 | 2.5 + 3 | 3 | 3 | 3 | 3 | 3 + 3.5000000000000000 | 3.5000000000000000 | 3.5000000000000000 | 3.5000000000000000 | 3.5 | 3.5 + 4 | 4 | 4 | 4 | 4 | 4 + 4.5000000000000000 | 4.5000000000000000 | 4.5000000000000000 | 4.5000000000000000 | 4.5 | 4.5 + 5 | 5 | 5 | 5 | 5 | 5 + 5.5000000000000000 | 5.5000000000000000 | 5.5000000000000000 | 5.5000000000000000 | 5.5 | 5.5 + 6 | 6 | 6 | 6 | 6 | 6 + (11 rows) + + drop table median_test; *** ./src/test/regress/sql/numeric.sql.orig 2010-09-25 21:21:11.087459138 +0200 --- ./src/test/regress/sql/numeric.sql 2010-09-26 12:27:12.226352249 +0200 *************** *** 824,826 **** --- 824,852 ---- select 12345678901234567890 / 123; select div(12345678901234567890, 123); select div(12345678901234567890, 123) * 123 + 12345678901234567890 % 123; + + -- median test + create table median_test (a int, b bigint, c smallint, d numeric, e double precision, f real); + insert into median_test select i,i,i,i,i,i from generate_series(1,10) g(i); + select median(a), + median(b), + median(c), + median(d), + median(e), + median(f) from median_test; + truncate table median_test; + insert into median_test select i,i,i,i,i,i from generate_series(1,11) g(i); + select median(a), + median(b), + median(c), + median(d), + median(e), + median(f) from median_test; + select median(a) over (order by a), + median(b) over (order by a), + median(c) over (order by a), + median(d) over (order by a), + median(e) over (order by a), + median(f) over (order by a) from median_test; + + drop table median_test;