*** a/doc/src/sgml/catalogs.sgml --- b/doc/src/sgml/catalogs.sgml *************** *** 352,358 **** aggtransfn regproc pg_proc.oid ! Transition function aggfinalfn --- 352,358 ---- aggtransfn regproc pg_proc.oid ! Transition function (zero if none) aggfinalfn *************** *** 370,376 **** aggtranstype oid pg_type.oid ! Data type of the aggregate function's internal transition (state) data agginitval --- 370,394 ---- aggtranstype oid pg_type.oid ! Data type of the aggregate function's internal transition (state) data (zero if none) ! ! ! aggtranssortop ! oid ! pg_operator.oid ! An optional sort operator for the type "aggtranstype", used for some kinds of ordered set functions ! ! ! aggordnargs ! int4 ! ! Number of direct arguments to ordered set function; -2 for hypothetical set functions; -1 for ordinary aggregates. ! ! ! aggisordsetfunc ! bool ! ! A flag to represent whether a function is ordered set or not agginitval *** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** *** 12197,12202 **** SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab; --- 12197,12453 ---- + + Ordered Set Functions + + + ordered set function + built-in + + + + Ordered set functions compute a single result + from an ordered set of input values. The built-in ordered set functions + are listed in + and + . + The special syntax considerations for ordered set functions + are explained in . + + + + Inverse Distribution Functions + + + + + Function + Direct Argument Type(s) + Ordered Argument Type(s) + Return Type + Description + + + + + + + + + percentile + discrete + + percentile_disc(fraction) WITHIN GROUP (ORDER BY sort_expression) + + + double precision (must be [0..1]) + + + any sortable type + + + same as sort expression + + + discrete percentile; returns the first result whose position in + the ordering equals or exceeds the specified fraction + + + + + + + percentile + discrete + + percentile_disc(fractions) WITHIN GROUP (ORDER BY sort_expression) + + + double precision[] (all must be [0..1] or null) + + + any sortable type + + + array of input type + + + multiple discrete percentile; returns an array of results matching the + shape of the fractions parameter, with each + non-null element replaced by the input value at that percentile + + + + + + + percentile + continuous + + + median + + percentile_cont(fraction) WITHIN GROUP (ORDER BY sort_expression) + + + double precision (must be [0..1]) + + + double precision or interval + + + same as sort expression + + + continuous percentile; interpolates between adjacent items. + + + + + + + percentile + continuous + + percentile_cont(fractions) WITHIN GROUP (ORDER BY sort_expression) + + + double precision[] (all must be [0..1] or null) + + + double precision or interval + + + array of input type + + + multiple continuous percentile; returns an array of results matching + the shape of the fractions parameter, with each + non-null element replaced by the value corresponding to that percentile + + + + + + + mode + statistical + + mode() WITHIN GROUP (ORDER BY sort_expression) + + + + + any sortable type + + + same as sort expression + + + returns the most frequent input value (choosing one arbitrarily if + there are multiple equally good result) + + + + + +
+ + + All the inverse distribution functions ignore null values in their sorted + input. The fraction parameter must be between 0 + and 1; an error is thrown if not. However, a null fraction simply produces + a null result. + + + + Hypothetical Set Functions + + + + + Function + Return Type + + + + + + + + + rank + hypothetical + + rank(args) WITHIN GROUP (ORDER BY sorted_args) + + + bigint + + + + + + + dense_rank + hypothetical + + dense_rank(args) WITHIN GROUP (ORDER BY sorted_args) + + + bigint + + + + + + + percent_rank + hypothetical + + percent_rank(args) WITHIN GROUP (ORDER BY sorted_args) + + + double precision + + + + + + + cume_dist + hypothetical + + cume_dist(args) WITHIN GROUP (ORDER BY sorted_args) + + + double precision + + + + + +
+ + + For all hypothetical set functions, the list of arguments given + by args should match the number and types of + arguments given as sorted_args. + + + + All of the functions listed in + are associated with a + window function defined in + . In each case, the function result + represents the value that the associated window function would have + returned, for the hypothetical row constructed from + args and included in the sorted group of + rows. + + +
+ Window Functions *** a/doc/src/sgml/ref/alter_aggregate.sgml --- b/doc/src/sgml/ref/alter_aggregate.sgml *************** *** 21,32 **** PostgreSQL documentation ! ALTER AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) RENAME TO new_name ALTER AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) OWNER TO new_owner ALTER AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) SET SCHEMA new_schema --- 21,37 ---- ! ALTER AGGREGATE RENAME TO new_name ALTER AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) OWNER TO new_owner ALTER AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) SET SCHEMA new_schema + + where aggregate_signature is one of: + + name ( * | [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) + name ( [ [ argmode ] [ arg_name ] arg_data_type [ , ... ] ] ) WITHIN GROUP ( * | [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) *************** *** 148,157 **** ALTER AGGREGATE myavg(integer) OWNER TO joe; ! To move the aggregate function myavg for type ! integer into schema myschema: ! ALTER AGGREGATE myavg(integer) SET SCHEMA myschema; --- 153,163 ---- ! To move the ordered set function mypercentile with ! direct argument of type float8 taking groups ! of integer type into schema myschema: ! ALTER AGGREGATE mypercentile(float8) WITHIN GROUP (integer) SET SCHEMA myschema; *** a/doc/src/sgml/ref/alter_extension.sgml --- b/doc/src/sgml/ref/alter_extension.sgml *************** *** 30,36 **** ALTER EXTENSION name DROP where member_object is: ! AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) | CAST (source_type AS target_type) | COLLATION object_name | CONVERSION object_name | --- 30,36 ---- where member_object is: ! AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) [ WITHIN GROUP ( * | [ argmode ] [ argname ] agg_type [, ...] ) ] | CAST (source_type AS target_type) | COLLATION object_name | CONVERSION object_name | *** a/doc/src/sgml/ref/comment.sgml --- b/doc/src/sgml/ref/comment.sgml *************** *** 23,29 **** PostgreSQL documentation COMMENT ON { ! AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) | CAST (source_type AS target_type) | COLLATION object_name | COLUMN relation_name.column_name | --- 23,29 ---- COMMENT ON { ! AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) [ WITHIN GROUP ( * | [ argmode ] [ argname ] agg_type [, ...] ) ] | CAST (source_type AS target_type) | COLLATION object_name | COLUMN relation_name.column_name | *** a/doc/src/sgml/ref/create_aggregate.sgml --- b/doc/src/sgml/ref/create_aggregate.sgml *************** *** 29,34 **** CREATE AGGREGATE name ( [ sort_operator ] ) + CREATE AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) WITHIN GROUP ( * | [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) ( + FINALFUNC = ffunc + [ , STRICT ] + [ , HYPOTHETICAL ] + [ , STYPE = state_data_type ] + [ , INITCOND = initial_condition ] + [ , TRANSSORTOP = state_sort_operator ] + ) + or the old syntax CREATE AGGREGATE name ( *************** *** 70,76 **** CREATE AGGREGATE name ( ! An aggregate function is made from one or two ordinary functions: a state transition function sfunc, --- 79,85 ---- ! An ordinary aggregate function is made from one or two ordinary functions: a state transition function sfunc, *************** *** 165,170 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; --- 174,187 ---- + The WITHIN GROUP syntax denotes a special subset of + aggregate functions collectively called ordered set + functions. These functions operate over groups of sorted values + in order-dependent ways. As such, they are constructed differently; there + is no state transition function, but the final function is required. + + + To be able to create an aggregate function, you must have USAGE privilege on the argument types, the state type, and the return type, as well as EXECUTE privilege *************** *** 278,283 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; --- 295,305 ---- aggregate's result, and the return type is state_data_type. + + For ordered set functions, the function arguments must instead + correspond to the input arguments (both direct and grouped) plus + the state type if any. + *************** *** 305,310 **** SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; --- 327,365 ---- + + + state_sort_operator + + + For ordered set functions only, this is a sort operator that can be + applied to + the state_data_type. + This is just an operator name (possibly schema-qualified). + + + + + + STRICT + + + For ordered set functions only, this flag specifies that the function is + strict, i.e. that grouped rows containing nulls are skipped. + + + + + + HYPOTHETICAL + + + For ordered set functions only, this flag specifies that the aggregate + parameters are to be processed according to the requirements for + hypothetical set functions. + + + *** a/doc/src/sgml/ref/drop_aggregate.sgml --- b/doc/src/sgml/ref/drop_aggregate.sgml *************** *** 21,29 **** PostgreSQL documentation ! DROP AGGREGATE [ IF EXISTS ] ! name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) [ CASCADE | RESTRICT ] --- 21,33 ---- ! DROP AGGREGATE [ IF EXISTS ] aggregate_signature [ CASCADE | RESTRICT ] + + where aggregate_signature is one of: + + name ( * | [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) + name ( [ [ argmode ] [ arg_name ] arg_data_type [ , ... ] ] ) WITHIN GROUP ( * | [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) *** a/doc/src/sgml/ref/security_label.sgml --- b/doc/src/sgml/ref/security_label.sgml *************** *** 25,31 **** SECURITY LABEL [ FOR provider ] ON { TABLE object_name | COLUMN table_name.column_name | ! AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) | DATABASE object_name | DOMAIN object_name | EVENT TRIGGER object_name | --- 25,31 ---- { TABLE object_name | COLUMN table_name.column_name | ! AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) [ WITHIN GROUP ( * | [ argmode ] [ argname ] agg_type [, ...] ) ] | DATABASE object_name | DOMAIN object_name | EVENT TRIGGER object_name | *** a/doc/src/sgml/syntax.sgml --- b/doc/src/sgml/syntax.sgml *************** *** 1706,1711 **** SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect --- 1706,1759 ---- + + Ordered Set Functions + + + ordered set function + + + + aggregate function + ordered set function + + + + WITHIN GROUP + + + + An ordered set function is a particular kind of + aggregate function which is applied to sorted groups of values and returns + a single result for each group which may be influenced by the sort + order. Like all aggregate functions, it reduces multiple inputs to a + single output value; typical ordered set functions return a percentile + extracted from the ordered group, or the rank a specified value would have + within that group. The syntax of an ordered set function is: + + + function_name ( [ expression [ , ... ] ] ) WITHIN GROUP ( order_by_clause ) [ FILTER ( WHERE filter_clause ) ] + + + where function_name is a previously + defined ordered set function (possibly qualified with a schema name) and + expression is any value expression that does + not itself contain an aggregate expression, a window function call, or any + reference to ungrouped columns of the source data. The + mandatory order_by_clause has the same syntax + as for a query-level ORDER BY clause, as described + in , except that its expressions are always + just expressions and cannot be output-column names or numbers. The + expressions of the order_by_clause may, and + almost invariably do, refer to the ungrouped columns of the input; the + clause defines the grouped input to the function. The optional + filter_clause is identical to that for + aggregate functions (see , and is applied + to input rows prior to the sort operation. + + + + Window Function Calls *** a/doc/src/sgml/xaggr.sgml --- b/doc/src/sgml/xaggr.sgml *************** *** 9,28 **** ! Aggregate functions in PostgreSQL ! are expressed in terms of state values ! and state transition functions. ! That is, an aggregate operates using a state value that is updated ! as each successive input row is processed. ! To define a new aggregate ! function, one selects a data type for the state value, ! an initial value for the state, and a state transition ! function. The state transition function is just an ! ordinary function that could also be used outside the ! context of the aggregate. A final function ! can also be specified, in case the desired result of the aggregate ! is different from the data that needs to be kept in the running ! state value. --- 9,25 ---- ! Aggregate functions (other than ordered set functions) ! in PostgreSQL are expressed in terms ! of state values and state transition ! functions. That is, an aggregate operates using a state value ! that is updated as each successive input row is processed. To define a new ! aggregate function, one selects a data type for the state value, an initial ! value for the state, and a state transition function. The state transition ! function is just an ordinary function that could also be used outside the ! context of the aggregate. A final function can also ! be specified, in case the desired result of the aggregate is different from ! the data that needs to be kept in the running state value. *** a/src/backend/catalog/pg_aggregate.c --- b/src/backend/catalog/pg_aggregate.c *************** *** 46,51 **** Oid --- 46,52 ---- AggregateCreate(const char *aggName, Oid aggNamespace, int numArgs, + int numDirectArgs, oidvector *parameterTypes, Datum allParameterTypes, Datum parameterModes, *************** *** 54,77 **** AggregateCreate(const char *aggName, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, Oid aggTransType, ! const char *agginitval) { Relation aggdesc; HeapTuple tup; bool nulls[Natts_pg_aggregate]; Datum values[Natts_pg_aggregate]; Form_pg_proc proc; ! Oid transfn; Oid finalfn = InvalidOid; /* can be omitted */ Oid sortop = InvalidOid; /* can be omitted */ Oid *aggArgTypes = parameterTypes->values; bool hasPolyArg; bool hasInternalArg; Oid rettype; Oid finaltype; ! Oid *fnArgs; ! int nargs_transfn; Oid procOid; TupleDesc tupDesc; int i; --- 55,83 ---- List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, + List *aggtranssortopName, Oid aggTransType, ! const char *agginitval, ! bool isStrict, ! bool isOrderedSet, ! bool isHypotheticalSet) { Relation aggdesc; HeapTuple tup; bool nulls[Natts_pg_aggregate]; Datum values[Natts_pg_aggregate]; Form_pg_proc proc; ! Oid transfn = InvalidOid; /* can be omitted */ Oid finalfn = InvalidOid; /* can be omitted */ Oid sortop = InvalidOid; /* can be omitted */ + Oid transsortop = InvalidOid; /* Can be omitted */ Oid *aggArgTypes = parameterTypes->values; bool hasPolyArg; bool hasInternalArg; + Oid variadic_type = InvalidOid; Oid rettype; Oid finaltype; ! Oid *fnArgs = palloc((numArgs + 1) * sizeof(Oid)); Oid procOid; TupleDesc tupDesc; int i; *************** *** 83,90 **** AggregateCreate(const char *aggName, if (!aggName) elog(ERROR, "no aggregate name supplied"); ! if (!aggtransfnName) ! elog(ERROR, "aggregate must have a transition function"); /* check for polymorphic and INTERNAL arguments */ hasPolyArg = false; --- 89,108 ---- if (!aggName) elog(ERROR, "no aggregate name supplied"); ! if (isOrderedSet) ! { ! if (aggtransfnName) ! elog(ERROR, "Ordered set functions cannot have transition functions"); ! if (!aggfinalfnName) ! elog(ERROR, "Ordered set functions must have final functions"); ! } ! else ! { ! if (!aggtransfnName) ! elog(ERROR, "aggregate must have a transition function"); ! if (isStrict) ! elog(ERROR, "aggregate with transition function must not be explicitly STRICT"); ! } /* check for polymorphic and INTERNAL arguments */ hasPolyArg = false; *************** *** 97,102 **** AggregateCreate(const char *aggName, --- 115,250 ---- hasInternalArg = true; } + /*- + * Argument mode checks. If there were no variadics, we should have been + * passed a NULL pointer for parameterModes, so we can skip this if so. + * Otherwise, the allowed cases are as follows: + * + * aggfn(..., variadic sometype) - normal agg with variadic arg last + * aggfn(..., variadic "any") - normal agg with "any" variadic + * + * ordfn(..., variadic "any") within group (*) + * - ordered set func with "any" variadic in direct args, which requires + * that the ordered args also be variadic any which we represent + * specially; this is the common case for hypothetical set functions. + * Note this is the only case where numDirectArgs == numArgs on input + * (implies finalfn(..., variadic "any")) + * + * ordfn(...) within group (..., variadic "any") + * - ordered set func with no variadic in direct args, but allowing any + * types of ordered args. + * (implies finalfn(..., ..., variadic "any")) + * + * We don't allow variadic ordered args other than "any"; we don't allow + * anything after variadic "any" except the special-case (*). + * + * We might like to support this one: + * + * ordfn(..., variadic sometype) within group (...) + * - ordered set func with variadic direct arg last, followed by ordered + * args, none of which are variadic + * (implies finalfn(..., sometype, ..., [transtype])) + * + * but currently it seems to be too intrusive to do so; the assumption + * that variadic args can only come last is quite widespread. + */ + + if (parameterModes != PointerGetDatum(NULL)) + { + /* + * We expect the array to be a 1-D CHAR array; verify that. We don't + * need to use deconstruct_array() since the array data is just going + * to look like a C array of char values. + */ + ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes); + char *paramModes; + int modesCount; + int i; + + if (ARR_NDIM(modesArray) != 1 || + ARR_HASNULL(modesArray) || + ARR_ELEMTYPE(modesArray) != CHAROID) + elog(ERROR, "parameterModes is not a 1-D char array"); + + paramModes = (char *) ARR_DATA_PTR(modesArray); + modesCount = ARR_DIMS(modesArray)[0]; + + for (i = 0; i < modesCount; ++i) + { + switch (paramModes[i]) + { + case PROARGMODE_VARIADIC: + if (OidIsValid(variadic_type)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("VARIADIC must not be specified more than once"))); + variadic_type = aggArgTypes[i]; + + /* enforce restrictions on ordered args */ + + if (numDirectArgs >= 0 + && i >= numDirectArgs + && variadic_type != ANYOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("VARIADIC ordered arguments must be of type ANY"))); + + break; + + case PROARGMODE_IN: + if (OidIsValid(variadic_type)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("VARIADIC argument must be last"))); + break; + + default: + elog(ERROR, "invalid argument mode"); + } + } + } + + switch (variadic_type) + { + case InvalidOid: + case ANYARRAYOID: + case ANYOID: + /* okay */ + break; + default: + if (!OidIsValid(get_element_type(variadic_type))) + elog(ERROR, "VARIADIC parameter must be an array"); + break; + } + + if (isHypotheticalSet) + { + if (numArgs != numDirectArgs + || variadic_type != ANYOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("Invalid argument types for hypothetical set function"), + errhint("Required declaration is (..., VARIADIC \"any\") WITHIN GROUP (*)"))); + + /* flag for special processing for hypothetical sets */ + numDirectArgs = -2; + } + else if (numArgs == numDirectArgs) + { + if (variadic_type == ANYOID) + { + /* + * this case allows the number of direct args to be truly variable + */ + numDirectArgs = -1; + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("Invalid argument types for ordered set function"), + errhint("WITHIN GROUP (*) is not allowed without VARIADIC \"any\""))); + } + /* * If transtype is polymorphic, must have polymorphic argument also; else * we will have no way to deduce the actual transtype. *************** *** 107,159 **** AggregateCreate(const char *aggName, errmsg("cannot determine transition data type"), errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument."))); ! /* find the transfn */ ! nargs_transfn = numArgs + 1; ! fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid)); ! fnArgs[0] = aggTransType; ! memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid)); ! transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs, ! &rettype); ! /* ! * Return type of transfn (possibly after refinement by ! * enforce_generic_type_consistency, if transtype isn't polymorphic) must ! * exactly match declared transtype. ! * ! * In the non-polymorphic-transtype case, it might be okay to allow a ! * rettype that's binary-coercible to transtype, but I'm not quite ! * convinced that it's either safe or useful. When transtype is ! * polymorphic we *must* demand exact equality. ! */ ! if (rettype != aggTransType) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("return type of transition function %s is not %s", ! NameListToString(aggtransfnName), ! format_type_be(aggTransType)))); ! tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn)); ! if (!HeapTupleIsValid(tup)) ! elog(ERROR, "cache lookup failed for function %u", transfn); ! proc = (Form_pg_proc) GETSTRUCT(tup); ! /* ! * If the transfn is strict and the initval is NULL, make sure first input ! * type and transtype are the same (or at least binary-compatible), so ! * that it's OK to use the first input value as the initial transValue. ! */ ! if (proc->proisstrict && agginitval == NULL) ! { ! if (numArgs < 1 || ! !IsBinaryCoercible(aggArgTypes[0], aggTransType)) ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type"))); } - ReleaseSysCache(tup); /* handle finalfn, if supplied */ ! if (aggfinalfnName) { fnArgs[0] = aggTransType; finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs, --- 255,340 ---- errmsg("cannot determine transition data type"), errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument."))); ! if (!isOrderedSet) ! { ! /* find the transfn */ ! fnArgs[0] = aggTransType; ! memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid)); ! transfn = lookup_agg_function(aggtransfnName, numArgs + 1, fnArgs, ! &rettype); ! /* ! * Return type of transfn (possibly after refinement by ! * enforce_generic_type_consistency, if transtype isn't polymorphic) ! * must exactly match declared transtype. ! * ! * In the non-polymorphic-transtype case, it might be okay to allow a ! * rettype that's binary-coercible to transtype, but I'm not quite ! * convinced that it's either safe or useful. When transtype is ! * polymorphic we *must* demand exact equality. ! */ ! if (rettype != aggTransType) ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("return type of transition function %s is not %s", ! NameListToString(aggtransfnName), ! format_type_be(aggTransType)))); ! ! tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn)); ! if (!HeapTupleIsValid(tup)) ! elog(ERROR, "cache lookup failed for function %u", transfn); ! proc = (Form_pg_proc) GETSTRUCT(tup); ! ! /* ! * If the transfn is strict and the initval is NULL, make sure first ! * input type and transtype are the same (or at least ! * binary-compatible), so that it's OK to use the first input value as ! * the initial transValue. ! */ ! if (proc->proisstrict && agginitval == NULL) ! { ! if (numArgs < 1 || ! !IsBinaryCoercible(aggArgTypes[0], aggTransType)) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type"))); ! } ! ReleaseSysCache(tup); } /* handle finalfn, if supplied */ ! if (isOrderedSet) ! { ! int num_final_args = numArgs; ! ! memcpy(fnArgs, aggArgTypes, num_final_args * sizeof(Oid)); ! ! /* ! * If there's a transtype, it becomes the last arg to the finalfn; ! * but if the agg (and hence the finalfn) is variadic "any", then ! * this contributes nothing to the signature. ! */ ! if (aggTransType != InvalidOid && variadic_type != ANYOID) ! fnArgs[num_final_args++] = aggTransType; ! ! finalfn = lookup_agg_function(aggfinalfnName, num_final_args, fnArgs, ! &finaltype); ! ! /* ! * this is also checked at runtime for security reasons, but check ! * here too to provide a friendly error (the requirement is because ! * the finalfn will be passed null dummy args for type resolution ! * purposes) ! */ ! ! if (func_strict(finalfn)) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("ordered set final functions must not be declared STRICT"))); ! } ! else if (aggfinalfnName) { fnArgs[0] = aggTransType; finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs, *************** *** 166,171 **** AggregateCreate(const char *aggName, --- 347,353 ---- */ finaltype = aggTransType; } + Assert(OidIsValid(finaltype)); /* *************** *** 207,212 **** AggregateCreate(const char *aggName, --- 389,406 ---- false, -1); } + /* handle transsortop, if supplied */ + if (aggtranssortopName) + { + if (!isOrderedSet || !OidIsValid(aggTransType)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("transition sort operator can only be specified for ordered set functions with transition types"))); + transsortop = LookupOperName(NULL, aggtranssortopName, + aggTransType, aggTransType, + false, -1); + } + /* * permission checks on used types */ *************** *** 217,231 **** AggregateCreate(const char *aggName, aclcheck_error_type(aclresult, aggArgTypes[i]); } ! aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE); ! if (aclresult != ACLCHECK_OK) ! aclcheck_error_type(aclresult, aggTransType); aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, finaltype); - /* * Everything looks okay. Try to create the pg_proc entry for the * aggregate. (This could fail if there's already a conflicting entry.) --- 411,427 ---- aclcheck_error_type(aclresult, aggArgTypes[i]); } ! if (OidIsValid(aggTransType)) ! { ! aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE); ! if (aclresult != ACLCHECK_OK) ! aclcheck_error_type(aclresult, aggTransType); ! } aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error_type(aclresult, finaltype); /* * Everything looks okay. Try to create the pg_proc entry for the * aggregate. (This could fail if there's already a conflicting entry.) *************** *** 246,252 **** AggregateCreate(const char *aggName, false, /* security invoker (currently not * definable for agg) */ false, /* isLeakProof */ ! false, /* isStrict (not needed for agg) */ PROVOLATILE_IMMUTABLE, /* volatility (not * needed for agg) */ parameterTypes, /* paramTypes */ --- 442,448 ---- false, /* security invoker (currently not * definable for agg) */ false, /* isLeakProof */ ! isStrict, /* isStrict (needed for ordered set funcs) */ PROVOLATILE_IMMUTABLE, /* volatility (not * needed for agg) */ parameterTypes, /* paramTypes */ *************** *** 272,278 **** AggregateCreate(const char *aggName, --- 468,478 ---- values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn); values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn); values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop); + values[Anum_pg_aggregate_aggtranssortop - 1] = ObjectIdGetDatum(transsortop); values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType); + values[Anum_pg_aggregate_aggordnargs - 1] = Int32GetDatum(numDirectArgs); + values[Anum_pg_aggregate_aggisordsetfunc - 1] = BoolGetDatum(isOrderedSet); + if (agginitval) values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval); else *************** *** 290,307 **** AggregateCreate(const char *aggName, /* * Create dependencies for the aggregate (above and beyond those already ! * made by ProcedureCreate). Note: we don't need an explicit dependency ! * on aggTransType since we depend on it indirectly through transfn. */ myself.classId = ProcedureRelationId; myself.objectId = procOid; myself.objectSubId = 0; /* Depends on transition function */ ! referenced.classId = ProcedureRelationId; ! referenced.objectId = transfn; ! referenced.objectSubId = 0; ! recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); /* Depends on final function, if any */ if (OidIsValid(finalfn)) --- 490,512 ---- /* * Create dependencies for the aggregate (above and beyond those already ! * made by ProcedureCreate). Normal aggs don't need an explicit ! * dependency on aggTransType since we depend on it indirectly through ! * transfn, but ordered set functions with variadic "any" do need one ! * (ordered set functions without variadic depend on it via the finalfn). */ myself.classId = ProcedureRelationId; myself.objectId = procOid; myself.objectSubId = 0; /* Depends on transition function */ ! if (OidIsValid(transfn)) ! { ! referenced.classId = ProcedureRelationId; ! referenced.objectId = transfn; ! referenced.objectSubId = 0; ! recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); ! } /* Depends on final function, if any */ if (OidIsValid(finalfn)) *************** *** 321,326 **** AggregateCreate(const char *aggName, --- 526,549 ---- recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + /* Depends on transsort operator, if any */ + if (OidIsValid(transsortop)) + { + referenced.classId = OperatorRelationId; + referenced.objectId = transsortop; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + /* May depend on aggTransType if any */ + if (OidIsValid(aggTransType) && isOrderedSet && variadic_type == ANYOID) + { + referenced.classId = TypeRelationId; + referenced.objectId = aggTransType; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + return procOid; } *** a/src/backend/commands/aggregatecmds.c --- b/src/backend/commands/aggregatecmds.c *************** *** 44,52 **** * DefineAggregate * * "oldstyle" signals the old (pre-8.2) style where the aggregate input type ! * is specified by a BASETYPE element in the parameters. Otherwise, ! * "args" is a list of FunctionParameter structs defining the agg's arguments. ! * "parameters" is a list of DefElem representing the agg's definition clauses. */ Oid DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, --- 44,55 ---- * DefineAggregate * * "oldstyle" signals the old (pre-8.2) style where the aggregate input type ! * is specified by a BASETYPE element in the parameters. Otherwise, "args" is ! * a pair, whose first element is a list of FunctionParameter structs defining ! * the agg's arguments (both direct and ordered), and whose second element is ! * an Integer node with the number of direct args, or -1 if this isn't an ! * ordered set func. "parameters" is a list of DefElem representing the agg's ! * definition clauses. */ Oid DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, *************** *** 58,75 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, List *transfuncName = NIL; List *finalfuncName = NIL; List *sortoperatorName = NIL; TypeName *baseType = NULL; TypeName *transType = NULL; char *initval = NULL; int numArgs; oidvector *parameterTypes; ArrayType *allParameterTypes; ArrayType *parameterModes; ArrayType *parameterNames; List *parameterDefaults; - Oid transTypeId; char transTypeType; ListCell *pl; /* Convert list of names to a name and namespace */ aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName); --- 61,83 ---- List *transfuncName = NIL; List *finalfuncName = NIL; List *sortoperatorName = NIL; + List *transsortoperatorName = NIL; TypeName *baseType = NULL; TypeName *transType = NULL; char *initval = NULL; int numArgs; + int numDirectArgs = -1; + Oid transTypeId = InvalidOid; oidvector *parameterTypes; ArrayType *allParameterTypes; ArrayType *parameterModes; ArrayType *parameterNames; List *parameterDefaults; char transTypeType; ListCell *pl; + bool ishypothetical = false; + bool isOrderedSet = false; + bool isStrict = false; /* Convert list of names to a name and namespace */ aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName); *************** *** 80,85 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, --- 88,101 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(aggNamespace)); + Assert(args == NIL || list_length(args) == 2); + + if (list_length(args) == 2) + { + numDirectArgs = intVal(lsecond(args)); + isOrderedSet = (numDirectArgs != -1); + } + foreach(pl, parameters) { DefElem *defel = (DefElem *) lfirst(pl); *************** *** 106,111 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, --- 122,133 ---- initval = defGetString(defel); else if (pg_strcasecmp(defel->defname, "initcond1") == 0) initval = defGetString(defel); + else if (pg_strcasecmp(defel->defname, "hypothetical") == 0) + ishypothetical = true; + else if (pg_strcasecmp(defel->defname, "strict") == 0) + isStrict = true; + else if (pg_strcasecmp(defel->defname, "transsortop") == 0) + transsortoperatorName = defGetQualifiedName(defel); else ereport(WARNING, (errcode(ERRCODE_SYNTAX_ERROR), *************** *** 113,129 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, defel->defname))); } ! /* ! * make sure we have our required definitions ! */ ! if (transType == NULL) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("aggregate stype must be specified"))); ! if (transfuncName == NIL) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("aggregate sfunc must be specified"))); /* * look up the aggregate's input datatype(s). --- 135,169 ---- defel->defname))); } ! if (!isOrderedSet) ! { ! /* ! * make sure we have our required definitions ! */ ! if (transType == NULL) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("aggregate stype must be specified"))); ! if (transfuncName == NIL) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("aggregate sfunc must be specified"))); ! if (isStrict) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("aggregate with sfunc must not be explicitly declared STRICT"))); ! } ! else ! { ! if (transfuncName != NIL) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("sfunc must not be specified for ordered set functions"))); ! if (finalfuncName == NIL) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("finalfunc must be specified for ordered set functions"))); ! } /* * look up the aggregate's input datatype(s). *************** *** 173,180 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("basetype is redundant with aggregate input type specification"))); ! numArgs = list_length(args); ! interpret_function_parameter_list(args, InvalidOid, true, /* is an aggregate */ queryString, --- 213,227 ---- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("basetype is redundant with aggregate input type specification"))); ! /* ! * The grammar has already concatenated the direct and ordered ! * args (if any) for us. Note that error checking for position ! * and number of VARIADIC args is not done for us, we have to ! * do it ourselves later (in AggregateCreate) ! */ ! ! numArgs = list_length(linitial(args)); ! interpret_function_parameter_list(linitial(args), InvalidOid, true, /* is an aggregate */ queryString, *************** *** 191,197 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, } /* ! * look up the aggregate's transtype. * * transtype can't be a pseudo-type, since we need to be able to store * values of the transtype. However, we can allow polymorphic transtype --- 238,244 ---- } /* ! * look up the aggregate's transtype, if specified. * * transtype can't be a pseudo-type, since we need to be able to store * values of the transtype. However, we can allow polymorphic transtype *************** *** 201,218 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, * worse) by connecting up incompatible internal-using functions in an * aggregate. */ ! transTypeId = typenameTypeId(NULL, transType); ! transTypeType = get_typtype(transTypeId); ! if (transTypeType == TYPTYPE_PSEUDO && ! !IsPolymorphicType(transTypeId)) { ! if (transTypeId == INTERNALOID && superuser()) ! /* okay */ ; ! else ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("aggregate transition data type cannot be %s", ! format_type_be(transTypeId)))); } /* --- 248,267 ---- * worse) by connecting up incompatible internal-using functions in an * aggregate. */ ! if (transType) { ! transTypeId = typenameTypeId(NULL, transType); ! transTypeType = get_typtype(transTypeId); ! if (transTypeType == TYPTYPE_PSEUDO && ! !IsPolymorphicType(transTypeId)) ! { ! if (transTypeId != INTERNALOID || !superuser() || isOrderedSet) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("aggregate transition data type cannot be %s", ! format_type_be(transTypeId)))); ! } ! } /* *************** *** 224,236 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, * value. However, if it's an incorrect value it seems much more * user-friendly to complain at CREATE AGGREGATE time. */ ! if (initval && transTypeType != TYPTYPE_PSEUDO) { ! Oid typinput, ! typioparam; ! getTypeInputInfo(transTypeId, &typinput, &typioparam); ! (void) OidInputFunctionCall(typinput, initval, typioparam, -1); } /* --- 273,295 ---- * value. However, if it's an incorrect value it seems much more * user-friendly to complain at CREATE AGGREGATE time. */ ! if (transType) { ! if (initval && transTypeType != TYPTYPE_PSEUDO) ! { ! Oid typinput, ! typioparam; ! getTypeInputInfo(transTypeId, &typinput, &typioparam); ! (void) OidInputFunctionCall(typinput, initval, typioparam, -1); ! } ! } ! else ! { ! if (initval) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), ! errmsg("INITVAL must not be specified without STYPE"))); } /* *************** *** 239,244 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, --- 298,304 ---- return AggregateCreate(aggName, /* aggregate name */ aggNamespace, /* namespace */ numArgs, + numDirectArgs, parameterTypes, PointerGetDatum(allParameterTypes), PointerGetDatum(parameterModes), *************** *** 247,252 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, transfuncName, /* step function name */ finalfuncName, /* final function name */ sortoperatorName, /* sort operator name */ transTypeId, /* transition data type */ ! initval); /* initial condition */ } --- 307,316 ---- transfuncName, /* step function name */ finalfuncName, /* final function name */ sortoperatorName, /* sort operator name */ + transsortoperatorName, /* transsort operator name */ transTypeId, /* transition data type */ ! initval, /* initial condition */ ! isStrict, /* is explicitly STRICT */ ! isOrderedSet, /* If the function is an ordered set */ ! ishypothetical); /* If the function is a hypothetical set */ } *** a/src/backend/commands/functioncmds.c --- b/src/backend/commands/functioncmds.c *************** *** 274,281 **** interpret_function_parameter_list(List *parameters, /* handle input parameters */ if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE) { ! /* other input parameters can't follow a VARIADIC parameter */ ! if (varCount > 0) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("VARIADIC parameter must be the last input parameter"))); --- 274,286 ---- /* handle input parameters */ if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE) { ! /* ! * For functions, other input parameters can't follow a VARIADIC ! * parameter; for aggregates, we might be dealing with an ordered ! * set function which have more complex rules for variadics, so ! * punt the error checking for that case to the caller. ! */ ! if (varCount > 0 && !is_aggregate) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("VARIADIC parameter must be the last input parameter"))); *** a/src/backend/executor/execQual.c --- b/src/backend/executor/execQual.c *************** *** 4410,4415 **** ExecInitExpr(Expr *node, PlanState *parent) --- 4410,4416 ---- astate->args = (List *) ExecInitExpr((Expr *) aggref->args, parent); + astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent); astate->aggfilter = ExecInitExpr(aggref->aggfilter, parent); *** a/src/backend/executor/functions.c --- b/src/backend/executor/functions.c *************** *** 380,387 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var) param = ParseFuncOrColumn(pstate, list_make1(subfield), list_make1(param), ! NIL, NULL, false, false, false, ! NULL, true, cref->location); } return param; --- 380,387 ---- param = ParseFuncOrColumn(pstate, list_make1(subfield), list_make1(param), ! cref->location, ! NULL); } return param; *** a/src/backend/executor/nodeAgg.c --- b/src/backend/executor/nodeAgg.c *************** *** 3,9 **** * nodeAgg.c * Routines to handle aggregate nodes. * ! * ExecAgg evaluates each aggregate in the following steps: * * transvalue = initcond * foreach input_tuple do --- 3,9 ---- * nodeAgg.c * Routines to handle aggregate nodes. * ! * ExecAgg evaluates each normal aggregate in the following steps: * * transvalue = initcond * foreach input_tuple do *************** *** 66,71 **** --- 66,91 ---- * AggState is available as context in earlier releases (back to 8.1), * but direct examination of the node is needed to use it before 9.0. * + *--- + * + * Ordered set functions modify the above process in a number of ways. + * Most importantly, they do not have transfuncs at all; the same sort + * mechanism used for ORDER BY/DISTINCT as described above is used to + * process the input, but then the finalfunc is called without actually + * running the sort (the finalfunc is allowed to insert rows first). + * The finalfunc has access via a set of AggSet* API functions to the + * Tuplesortstate, row count in the group, and other ancillary info. + * + * Ordered set functions can, however, have a transvalue declared; this is + * treated as a constant, and added to the end of the sort fields. + * Hypothetical set functions use this to provide a flag that distinguishes + * the hypothetical row from the input data. + * + * Since they have no transfunc, ordered set functions have their own + * 'strict' flag stored in the aggregate's own pg_proc entry; this affects + * whether rows containing nulls are placed in the sorter. But since we + * pass dummy null arguments to the finalfunc for type resolution purposes, + * no ordered set finalfunc is allowed to be strict. * * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California *************** *** 87,96 **** --- 107,118 ---- #include "executor/nodeAgg.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" + #include "nodes/makefuncs.h" #include "optimizer/clauses.h" #include "optimizer/tlist.h" #include "parser/parse_agg.h" #include "parser/parse_coerce.h" + #include "parser/parse_clause.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" *************** *** 105,110 **** --- 127,134 ---- */ typedef struct AggStatePerAggData { + NodeTag type; + /* * These values are set up during ExecInitAgg() and do not change * thereafter: *************** *** 114,123 **** typedef struct AggStatePerAggData AggrefExprState *aggrefstate; Aggref *aggref; ! /* number of input arguments for aggregate function proper */ int numArguments; ! /* number of inputs including ORDER BY expressions */ int numInputs; /* Oids of transfer functions */ --- 138,162 ---- AggrefExprState *aggrefstate; Aggref *aggref; ! /* Pointer to parent AggState node */ ! AggState *aggstate; ! ! /* copied from aggref */ ! bool isOrderedSet; ! ! /* ! * number of arguments for aggregate function proper. ! * For ordered set functions, this includes the ORDER BY ! * columns, *except* in the case of hypothetical set functions. ! */ int numArguments; ! /* ! * number of inputs including ORDER BY expressions. For ordered ! * set functions, *only* the ORDER BY expressions are included ! * here, since the direct args to the function are not properly ! * "input" in the sense of being derived from the tuple group. ! */ int numInputs; /* Oids of transfer functions */ *************** *** 126,137 **** typedef struct AggStatePerAggData /* * fmgr lookup data for transfer functions --- only valid when ! * corresponding oid is not InvalidOid. Note in particular that fn_strict ! * flags are kept here. */ FmgrInfo transfn; FmgrInfo finalfn; /* Input collation derived for aggregate */ Oid aggCollation; --- 165,187 ---- /* * fmgr lookup data for transfer functions --- only valid when ! * corresponding oid is not InvalidOid. */ FmgrInfo transfn; FmgrInfo finalfn; + /* + * If >0, aggregate as a whole is strict (skips null input) + * The value specifies how many columns to check; normal aggs + * only check numArguments, while ordered set functions check + * numInputs. + * + * Ordered set functions are not allowed to have strict finalfns; + * other aggregates respect the finalfn strict flag in the + * FmgrInfo above. + */ + int numStrict; + /* Input collation derived for aggregate */ Oid aggCollation; *************** *** 148,153 **** typedef struct AggStatePerAggData --- 198,206 ---- Oid *sortCollations; bool *sortNullsFirst; + /* just for convenience of ordered set funcs, not used here */ + Oid *sortEqOperators; + /* * fmgr lookup data for input columns' equality operators --- only * set/used when aggregate has DISTINCT flag. Note that these are in *************** *** 204,209 **** typedef struct AggStatePerAggData --- 257,265 ---- */ Tuplesortstate *sortstate; /* sort object, if DISTINCT or ORDER BY */ + + int64 number_of_rows; /* number of rows */ + } AggStatePerAggData; /* *************** *** 300,305 **** initialize_aggregates(AggState *aggstate, --- 356,363 ---- AggStatePerAgg peraggstate = &peragg[aggno]; AggStatePerGroup pergroupstate = &pergroup[aggno]; + peraggstate->number_of_rows = 0; + /* * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate. */ *************** *** 383,396 **** advance_transition_function(AggState *aggstate, MemoryContext oldContext; Datum newVal; int i; ! if (peraggstate->transfn.fn_strict) { /* * For a strict transfn, nothing happens when there's a NULL input; we * just keep the prior transValue. */ ! for (i = 1; i <= numArguments; i++) { if (fcinfo->argnull[i]) return; --- 441,457 ---- MemoryContext oldContext; Datum newVal; int i; + int numStrict = peraggstate->numStrict; ! Assert(OidIsValid(peraggstate->transfn_oid)); ! ! if (numStrict > 0) { /* * For a strict transfn, nothing happens when there's a NULL input; we * just keep the prior transValue. */ ! for (i = 1; i <= numStrict; i++) { if (fcinfo->argnull[i]) return; *************** *** 506,529 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup) if (peraggstate->numSortCols > 0) { /* DISTINCT and/or ORDER BY case */ Assert(slot->tts_nvalid == peraggstate->numInputs); /* ! * If the transfn is strict, we want to check for nullity before * storing the row in the sorter, to save space if there are a lot ! * of nulls. Note that we must only check numArguments columns, ! * not numInputs, since nullity in columns used only for sorting ! * is not relevant here. */ ! if (peraggstate->transfn.fn_strict) { ! for (i = 0; i < nargs; i++) { if (slot->tts_isnull[i]) break; } ! if (i < nargs) continue; } --- 567,590 ---- if (peraggstate->numSortCols > 0) { + int numStrict = peraggstate->numStrict; + /* DISTINCT and/or ORDER BY case */ Assert(slot->tts_nvalid == peraggstate->numInputs); /* ! * If the aggregate is strict, we want to check for nullity before * storing the row in the sorter, to save space if there are a lot ! * of nulls. */ ! if (numStrict > 0) { ! for (i = 0; i < numStrict; i++) { if (slot->tts_isnull[i]) break; } ! if (i < numStrict) continue; } *************** *** 534,539 **** advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup) --- 595,602 ---- slot->tts_isnull[0]); else tuplesort_puttupleslot(peraggstate->sortstate, slot); + + peraggstate->number_of_rows++; } else { *************** *** 756,770 **** finalize_aggregate(AggState *aggstate, if (OidIsValid(peraggstate->finalfn_oid)) { FunctionCallInfoData fcinfo; ! InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1, ! peraggstate->aggCollation, ! (void *) aggstate, NULL); ! fcinfo.arg[0] = pergroupstate->transValue; ! fcinfo.argnull[0] = pergroupstate->transValueIsNull; ! if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull) { ! /* don't call a strict function with NULL inputs */ *resultVal = (Datum) 0; *resultIsNull = true; } --- 819,884 ---- if (OidIsValid(peraggstate->finalfn_oid)) { FunctionCallInfoData fcinfo; + bool isnull = false; + + if (!(peraggstate->isOrderedSet)) + { + InitFunctionCallInfoData(fcinfo, + &(peraggstate->finalfn), + 1, + peraggstate->aggCollation, + (void *) aggstate, + NULL); + + fcinfo.arg[0] = pergroupstate->transValue; + fcinfo.argnull[0] = isnull = pergroupstate->transValueIsNull; + } + else + { + List *args = peraggstate->aggrefstate->orddirectargs; + ListCell *lc; + int i = 0; + int numArguments = peraggstate->numArguments; + + ExecClearTuple(peraggstate->evalslot); + ExecClearTuple(peraggstate->uniqslot); + + InitFunctionCallInfoData(fcinfo, + &(peraggstate->finalfn), + peraggstate->numArguments, + peraggstate->aggCollation, + (void *) peraggstate, + NULL); + + foreach (lc, args) + { + ExprState *expr = (ExprState *) lfirst(lc); + + fcinfo.arg[i] = ExecEvalExpr(expr, + aggstate->ss.ps.ps_ExprContext, + &fcinfo.argnull[i], + NULL); + if (fcinfo.argnull[i]) + isnull = true; ! ++i; ! } ! ! for(; i < numArguments; i++) ! { ! fcinfo.arg[i] = (Datum) 0; ! fcinfo.argnull[i] = true; ! isnull = true; ! } ! } ! ! if (isnull && fcinfo.flinfo->fn_strict) { ! /* ! * don't call a strict function with NULL inputs; for ordered set ! * functions this is paranoia, we already required that fn_strict ! * is false, but easy to check anyway ! */ *resultVal = (Datum) 0; *resultIsNull = true; } *************** *** 1164,1169 **** agg_retrieve_direct(AggState *aggstate) --- 1278,1294 ---- } /* + * Use the representative input tuple for any references to + * non-aggregated input columns in the qual and tlist. (If we are not + * grouping, and there are no input rows at all, we will come here + * with an empty firstSlot ... but if not grouping, there can't be any + * references to non-aggregated input columns, so no problem.) + * We do this before finalizing because for ordered set functions, + * finalize_aggregates can evaluate arguments referencing the tuple. + */ + econtext->ecxt_outertuple = firstSlot; + + /* * Done scanning input tuple group. Finalize each aggregate * calculation, and stash results in the per-output-tuple context. */ *************** *** 1174,1187 **** agg_retrieve_direct(AggState *aggstate) if (peraggstate->numSortCols > 0) { ! if (peraggstate->numInputs == 1) ! process_ordered_aggregate_single(aggstate, ! peraggstate, ! pergroupstate); ! else ! process_ordered_aggregate_multi(aggstate, ! peraggstate, ! pergroupstate); } finalize_aggregate(aggstate, peraggstate, pergroupstate, --- 1299,1315 ---- if (peraggstate->numSortCols > 0) { ! if (!(peraggstate->isOrderedSet)) ! { ! if (peraggstate->numInputs == 1) ! process_ordered_aggregate_single(aggstate, ! peraggstate, ! pergroupstate); ! else ! process_ordered_aggregate_multi(aggstate, ! peraggstate, ! pergroupstate); ! } } finalize_aggregate(aggstate, peraggstate, pergroupstate, *************** *** 1189,1203 **** agg_retrieve_direct(AggState *aggstate) } /* - * Use the representative input tuple for any references to - * non-aggregated input columns in the qual and tlist. (If we are not - * grouping, and there are no input rows at all, we will come here - * with an empty firstSlot ... but if not grouping, there can't be any - * references to non-aggregated input columns, so no problem.) - */ - econtext->ecxt_outertuple = firstSlot; - - /* * Check the qual (HAVING clause); if the group does not match, ignore * it and loop back to try to process another group. */ --- 1317,1322 ---- *************** *** 1568,1577 **** ExecInitAgg(Agg *node, EState *estate, int eflags) --- 1687,1698 ---- int numInputs; int numSortCols; int numDistinctCols; + bool isOrderedSet = aggref->isordset; List *sortlist; HeapTuple aggTuple; Form_pg_aggregate aggform; Oid aggtranstype; + Oid aggtranstypecoll; AclResult aclresult; Oid transfn_oid, finalfn_oid; *************** *** 1580,1585 **** ExecInitAgg(Agg *node, EState *estate, int eflags) --- 1701,1710 ---- Datum textInitVal; int i; ListCell *lc; + bool is_strict; + Oid inputCollations[FUNC_MAX_ARGS]; + List *argexprs; + List *argexprstate; /* Planner should have assigned aggregate to correct level */ Assert(aggref->agglevelsup == 0); *************** *** 1601,1631 **** ExecInitAgg(Agg *node, EState *estate, int eflags) /* Nope, so assign a new PerAgg record */ peraggstate = &peragg[++aggno]; - /* Mark Aggref state node with assigned index in the result array */ - aggrefstate->aggno = aggno; - /* Fill in the peraggstate data */ ! peraggstate->aggrefstate = aggrefstate; peraggstate->aggref = aggref; ! numInputs = list_length(aggref->args); ! peraggstate->numInputs = numInputs; ! peraggstate->sortstate = NULL; ! /* ! * Get actual datatypes of the inputs. These could be different from ! * the agg's declared input types, when the agg accepts ANY or a ! * polymorphic type. ! */ ! numArguments = 0; ! foreach(lc, aggref->args) ! { ! TargetEntry *tle = (TargetEntry *) lfirst(lc); ! if (!tle->resjunk) ! inputTypes[numArguments++] = exprType((Node *) tle->expr); ! } ! peraggstate->numArguments = numArguments; aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(aggref->aggfnoid)); if (!HeapTupleIsValid(aggTuple)) --- 1726,1743 ---- /* Nope, so assign a new PerAgg record */ peraggstate = &peragg[++aggno]; /* Fill in the peraggstate data */ ! peraggstate->type = T_AggStatePerAggData; ! peraggstate->aggstate = aggstate; peraggstate->aggref = aggref; ! peraggstate->aggrefstate = aggrefstate; ! peraggstate->isOrderedSet = isOrderedSet; ! /* Mark Aggref state node with assigned index in the result array */ ! aggrefstate->aggno = aggno; + /* Fetch the pg_aggregate row */ aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(aggref->aggfnoid)); if (!HeapTupleIsValid(aggTuple)) *************** *** 1633,1638 **** ExecInitAgg(Agg *node, EState *estate, int eflags) --- 1745,1757 ---- aggref->aggfnoid); aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple); + /* + * Check that the definition hasn't somehow changed incompatibly. + */ + if (isOrderedSet != (aggform->aggisordsetfunc) + || (aggref->ishypothetical != (aggform->aggordnargs == -2))) + elog(ERROR, "Incompatible change to aggregate definition"); + /* Check permission to call aggregate function */ aclresult = pg_proc_aclcheck(aggref->aggfnoid, GetUserId(), ACL_EXECUTE); *************** *** 1644,1668 **** ExecInitAgg(Agg *node, EState *estate, int eflags) peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn; peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn; ! /* Check that aggregate owner has permission to call component fns */ { HeapTuple procTuple; Oid aggOwner; procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(aggref->aggfnoid)); if (!HeapTupleIsValid(procTuple)) elog(ERROR, "cache lookup failed for function %u", aggref->aggfnoid); ! aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner; ReleaseSysCache(procTuple); ! aclresult = pg_proc_aclcheck(transfn_oid, aggOwner, ! ACL_EXECUTE); ! if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(transfn_oid)); ! InvokeFunctionExecuteHook(transfn_oid); if (OidIsValid(finalfn_oid)) { aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner, --- 1763,1799 ---- peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn; peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn; ! /* ! * Check that aggregate owner has permission to call component fns ! * In passing, fetch the proisstrict flag for the aggregate proper, ! * which subs for the transfn's strictness flag in cases where there ! * is no transfn. ! */ { HeapTuple procTuple; Oid aggOwner; + Form_pg_proc procp; procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(aggref->aggfnoid)); if (!HeapTupleIsValid(procTuple)) elog(ERROR, "cache lookup failed for function %u", aggref->aggfnoid); ! procp = (Form_pg_proc) GETSTRUCT(procTuple); ! aggOwner = procp->proowner; ! is_strict = procp->proisstrict; ReleaseSysCache(procTuple); ! if (OidIsValid(transfn_oid)) ! { ! aclresult = pg_proc_aclcheck(transfn_oid, aggOwner, ! ACL_EXECUTE); ! if (aclresult != ACLCHECK_OK && OidIsValid(transfn_oid)) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(transfn_oid)); ! InvokeFunctionExecuteHook(transfn_oid); ! } ! if (OidIsValid(finalfn_oid)) { aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner, *************** *** 1674,1690 **** ExecInitAgg(Agg *node, EState *estate, int eflags) } } /* resolve actual type of transition state, if polymorphic */ aggtranstype = aggform->aggtranstype; ! if (IsPolymorphicType(aggtranstype)) { /* have to fetch the agg's declared input types... */ Oid *declaredArgTypes; ! int agg_nargs; (void) get_func_signature(aggref->aggfnoid, ! &declaredArgTypes, &agg_nargs); ! Assert(agg_nargs == numArguments); aggtranstype = enforce_generic_type_consistency(inputTypes, declaredArgTypes, agg_nargs, --- 1805,1841 ---- } } + /* + * Get actual datatypes of the inputs. These could be different from + * the agg's declared input types, when the agg accepts ANY or a + * polymorphic type. + */ + + peraggstate->numInputs = numInputs = list_length(aggref->args); + + numArguments = get_aggregate_argtypes(aggref, + inputTypes, + inputCollations); + /* resolve actual type of transition state, if polymorphic */ aggtranstype = aggform->aggtranstype; ! if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype)) { /* have to fetch the agg's declared input types... */ Oid *declaredArgTypes; ! int agg_nargs; (void) get_func_signature(aggref->aggfnoid, ! &declaredArgTypes, ! &agg_nargs); ! ! /* ! * if variadic "any", might be more actual args than declared ! * args, but these extra args can't influence the determination ! * of polymorphic transition or result type. ! */ ! Assert(agg_nargs <= numArguments); ! aggtranstype = enforce_generic_type_consistency(inputTypes, declaredArgTypes, agg_nargs, *************** *** 1693,1727 **** ExecInitAgg(Agg *node, EState *estate, int eflags) pfree(declaredArgTypes); } /* build expression trees using actual argument & result types */ ! build_aggregate_fnexprs(inputTypes, ! numArguments, ! aggref->aggvariadic, ! aggtranstype, ! aggref->aggtype, ! aggref->inputcollid, ! transfn_oid, ! finalfn_oid, ! &transfnexpr, ! &finalfnexpr); ! ! fmgr_info(transfn_oid, &peraggstate->transfn); ! fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn); if (OidIsValid(finalfn_oid)) { fmgr_info(finalfn_oid, &peraggstate->finalfn); fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn); } peraggstate->aggCollation = aggref->inputcollid; get_typlenbyval(aggref->aggtype, &peraggstate->resulttypeLen, &peraggstate->resulttypeByVal); ! get_typlenbyval(aggtranstype, ! &peraggstate->transtypeLen, ! &peraggstate->transtypeByVal); /* * initval is potentially null, so don't try to access it as a struct --- 1844,1925 ---- pfree(declaredArgTypes); } + aggtranstypecoll = get_typcollation(aggtranstype); + /* build expression trees using actual argument & result types */ ! ! if (!isOrderedSet) ! { ! build_aggregate_fnexprs(inputTypes, ! numArguments, ! aggref->aggvariadic, ! aggtranstype, ! aggref->aggtype, ! aggref->inputcollid, ! transfn_oid, ! finalfn_oid, ! &transfnexpr, ! &finalfnexpr); ! } ! else ! { ! /* ! * The transvalue counts as an argument, but not for hypothetical ! * set funcs. ! */ ! if (OidIsValid(aggtranstype) && !(aggref->ishypothetical)) ! { ! if (numArguments == FUNC_MAX_ARGS) ! elog(ERROR, "Too many arguments to ordered set function"); ! ! inputTypes[numArguments++] = aggtranstype; ! inputCollations[numArguments++] = aggtranstypecoll; ! } ! ! build_orderedset_fnexprs(inputTypes, ! numArguments, ! aggref->aggvariadic, ! aggref->aggtype, ! aggref->inputcollid, ! inputCollations, ! finalfn_oid, ! &finalfnexpr); ! } ! ! peraggstate->numArguments = numArguments; ! ! if (OidIsValid(transfn_oid)) ! { ! fmgr_info(transfn_oid, &peraggstate->transfn); ! fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn); ! ! is_strict = peraggstate->transfn.fn_strict; ! } if (OidIsValid(finalfn_oid)) { fmgr_info(finalfn_oid, &peraggstate->finalfn); fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn); + if (peraggstate->finalfn.fn_strict && isOrderedSet) + elog(ERROR, "Ordered set finalfns must not be strict"); } + if (is_strict) + peraggstate->numStrict = (isOrderedSet ? numInputs : numArguments); + else + peraggstate->numStrict = 0; + peraggstate->aggCollation = aggref->inputcollid; get_typlenbyval(aggref->aggtype, &peraggstate->resulttypeLen, &peraggstate->resulttypeByVal); ! if (OidIsValid(aggtranstype)) ! { ! get_typlenbyval(aggtranstype, ! &peraggstate->transtypeLen, ! &peraggstate->transtypeByVal); ! } /* * initval is potentially null, so don't try to access it as a struct *************** *** 1744,1750 **** ExecInitAgg(Agg *node, EState *estate, int eflags) * transValue. This should have been checked at agg definition time, * but just in case... */ ! if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull) { if (numArguments < 1 || !IsBinaryCoercible(inputTypes[0], aggtranstype)) --- 1942,1950 ---- * transValue. This should have been checked at agg definition time, * but just in case... */ ! if (OidIsValid(peraggstate->transfn_oid) ! && peraggstate->transfn.fn_strict ! && peraggstate->initValueIsNull) { if (numArguments < 1 || !IsBinaryCoercible(inputTypes[0], aggtranstype)) *************** *** 1754,1774 **** ExecInitAgg(Agg *node, EState *estate, int eflags) aggref->aggfnoid))); } ! /* ! * Get a tupledesc corresponding to the inputs (including sort ! * expressions) of the agg. ! */ ! peraggstate->evaldesc = ExecTypeFromTL(aggref->args, false); ! ! /* Create slot we're going to do argument evaluation in */ ! peraggstate->evalslot = ExecInitExtraTupleSlot(estate); ! ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc); ! ! /* Set up projection info for evaluation */ ! peraggstate->evalproj = ExecBuildProjectionInfo(aggrefstate->args, ! aggstate->tmpcontext, ! peraggstate->evalslot, ! NULL); /* * If we're doing either DISTINCT or ORDER BY, then we have a list of --- 1954,1961 ---- aggref->aggfnoid))); } ! argexprs = aggref->args; ! argexprstate = aggrefstate->args; /* * If we're doing either DISTINCT or ORDER BY, then we have a list of *************** *** 1777,1782 **** ExecInitAgg(Agg *node, EState *estate, int eflags) --- 1964,1974 ---- * * Note that by construction, if there is a DISTINCT clause then the * ORDER BY clause is a prefix of it (see transformDistinctClause). + * + * If we're doing an ordered set function, though, we want to do the + * initialization for DISTINCT since the ordered set finalfn might + * want it, and it's much easier to do it here. So set numDistinctCols + * and let the later initialization take care of it. */ if (aggref->aggdistinct) { *************** *** 1788,1798 **** ExecInitAgg(Agg *node, EState *estate, int eflags) { sortlist = aggref->aggorder; numSortCols = list_length(sortlist); ! numDistinctCols = 0; } peraggstate->numSortCols = numSortCols; peraggstate->numDistinctCols = numDistinctCols; if (numSortCols > 0) { --- 1980,2065 ---- { sortlist = aggref->aggorder; numSortCols = list_length(sortlist); ! numDistinctCols = isOrderedSet ? numSortCols : 0; ! } ! ! /* ! * If this is an ordered set function, and we have a transtype, then ! * it represents an extra column to be added to the sorter with a ! * fixed value. Plus, if aggtranssortop is valid, we have to include ! * a sort entry for the new column. ! * ! * I'd probably have done this in the planner if I'd seen any ! * possible place to put it; if there is one, it's very obscure. ! */ ! ! if (OidIsValid(aggtranstype) && isOrderedSet) ! { ! Oid sortop = aggform->aggtranssortop; ! Const *node = makeNode(Const); ! TargetEntry *tle; ! SortGroupClause *sortcl = NULL; ! ! node->consttype = aggtranstype; ! node->consttypmod = -1; ! node->constcollid = aggtranstypecoll; ! node->constlen = peraggstate->transtypeLen; ! node->constvalue = peraggstate->initValue; ! node->constisnull = peraggstate->initValueIsNull; ! node->constbyval = peraggstate->transtypeByVal; ! node->location = -1; ! ! tle = makeTargetEntry((Expr *) node, ! ++numInputs, ! NULL, ! true); ! ! peraggstate->numInputs = numInputs; ! ! if (OidIsValid(sortop)) ! { ! Assert(aggref->aggdistinct == NIL); ! ! sortcl = makeNode(SortGroupClause); ! ! sortcl->tleSortGroupRef = assignSortGroupRef(tle, argexprs); ! ! sortcl->sortop = sortop; ! sortcl->hashable = false; ! sortcl->eqop = get_equality_op_for_ordering_op(sortop, ! &sortcl->nulls_first); ! ! sortlist = lappend(list_copy(sortlist), sortcl); ! ++numSortCols; ! ++numDistinctCols; ! } ! ! /* shallow-copy the passed-in lists, which we must not scribble on. */ ! ! argexprs = lappend(list_copy(argexprs), (Node *) tle); ! argexprstate = lappend(list_copy(argexprstate), ! ExecInitExpr((Expr *) tle, (PlanState *) aggstate)); } peraggstate->numSortCols = numSortCols; peraggstate->numDistinctCols = numDistinctCols; + peraggstate->sortstate = NULL; + + /* + * Get a tupledesc corresponding to the inputs (including sort + * expressions) of the agg. + */ + peraggstate->evaldesc = ExecTypeFromTL(argexprs, false); + + /* Create slot we're going to do argument evaluation in */ + peraggstate->evalslot = ExecInitExtraTupleSlot(estate); + ExecSetSlotDescriptor(peraggstate->evalslot, peraggstate->evaldesc); + + /* Set up projection info for evaluation */ + peraggstate->evalproj = ExecBuildProjectionInfo(argexprstate, + aggstate->tmpcontext, + peraggstate->evalslot, + NULL); if (numSortCols > 0) { *************** *** 1805,1815 **** ExecInitAgg(Agg *node, EState *estate, int eflags) /* If we have only one input, we need its len/byval info. */ if (numInputs == 1) { ! get_typlenbyval(inputTypes[0], &peraggstate->inputtypeLen, &peraggstate->inputtypeByVal); } ! else if (numDistinctCols > 0) { /* we will need an extra slot to store prior values */ peraggstate->uniqslot = ExecInitExtraTupleSlot(estate); --- 2072,2083 ---- /* If we have only one input, we need its len/byval info. */ if (numInputs == 1) { ! get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid, &peraggstate->inputtypeLen, &peraggstate->inputtypeByVal); } ! ! if (numDistinctCols > 0 && (numInputs > 1 || isOrderedSet)) { /* we will need an extra slot to store prior values */ peraggstate->uniqslot = ExecInitExtraTupleSlot(estate); *************** *** 1822,1871 **** ExecInitAgg(Agg *node, EState *estate, int eflags) (AttrNumber *) palloc(numSortCols * sizeof(AttrNumber)); peraggstate->sortOperators = (Oid *) palloc(numSortCols * sizeof(Oid)); peraggstate->sortCollations = (Oid *) palloc(numSortCols * sizeof(Oid)); peraggstate->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)); peraggstate->sortColIdx[i] = tle->resno; peraggstate->sortOperators[i] = sortcl->sortop; peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr); peraggstate->sortNullsFirst[i] = sortcl->nulls_first; - i++; - } - Assert(i == numSortCols); - } ! if (aggref->aggdistinct) ! { ! Assert(numArguments > 0); ! ! /* ! * We need the equal function for each DISTINCT comparison we will ! * make. ! */ ! peraggstate->equalfns = ! (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo)); ! ! i = 0; ! foreach(lc, aggref->aggdistinct) ! { ! SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc); - fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]); i++; } ! Assert(i == numDistinctCols); } ReleaseSysCache(aggTuple); --- 2090,2136 ---- (AttrNumber *) palloc(numSortCols * sizeof(AttrNumber)); peraggstate->sortOperators = (Oid *) palloc(numSortCols * sizeof(Oid)); + peraggstate->sortEqOperators = + (Oid *) palloc(numSortCols * sizeof(Oid)); peraggstate->sortCollations = (Oid *) palloc(numSortCols * sizeof(Oid)); peraggstate->sortNullsFirst = (bool *) palloc(numSortCols * sizeof(bool)); + if (numDistinctCols > 0) + peraggstate->equalfns = + (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo)); + else + peraggstate->equalfns = NULL; + i = 0; foreach(lc, sortlist) { SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc); TargetEntry *tle = get_sortgroupclause_tle(sortcl, ! argexprs); /* the parser should have made sure of this */ Assert(OidIsValid(sortcl->sortop)); peraggstate->sortColIdx[i] = tle->resno; peraggstate->sortOperators[i] = sortcl->sortop; + peraggstate->sortEqOperators[i] = sortcl->eqop; peraggstate->sortCollations[i] = exprCollation((Node *) tle->expr); peraggstate->sortNullsFirst[i] = sortcl->nulls_first; ! /* ! * It's OK to get the equalfns here too, since we already ! * require that sortlist is aggref->aggdistinct for the normal ! * distinct case, and for ordered set functions using the ! * (possibly modified copy of) aggref->aggorder is correct ! */ ! if (peraggstate->equalfns) ! fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]); i++; } ! Assert(i == numSortCols); } ReleaseSysCache(aggTuple); *************** *** 2023,2028 **** ExecReScanAgg(AggState *node) --- 2288,2296 ---- * If aggcontext isn't NULL, the function also stores at *aggcontext the * identity of the memory context that aggregate transition values are * being stored in. + * + * We do NOT include AGG_CONTEXT_ORDERED as a possible return here, since + * that would open a security hole. */ int AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext) *************** *** 2063,2065 **** aggregate_dummy(PG_FUNCTION_ARGS) --- 2331,2448 ---- fcinfo->flinfo->fn_oid); return (Datum) 0; /* keep compiler quiet */ } + + /* AggSetGetRowCount - Get the number of rows in case of ordered set + * functions. + */ + int64 + AggSetGetRowCount(FunctionCallInfo fcinfo) + { + if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData)) + { + return ((AggStatePerAggData *)fcinfo->context)->number_of_rows; + } + + elog(ERROR, "Called AggSetGetRowCount on non ordered set function"); + return -1; + } + + /* AggSetGetSortInfo - Get the sort state in the case of + * ordered set functions. + */ + void + AggSetGetSortInfo(FunctionCallInfo fcinfo, + Tuplesortstate **sortstate, + TupleDesc *tupdesc, + TupleTableSlot **tupslot, + Oid *datumtype) + { + if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData)) + { + AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context; + + *sortstate = peraggstate->sortstate; + if (peraggstate->numInputs == 1) + { + if (tupdesc) + *tupdesc = NULL; + if (datumtype) + *datumtype = peraggstate->evaldesc->attrs[0]->atttypid; + } + else + { + if (tupdesc) + *tupdesc = peraggstate->evaldesc; + if (datumtype) + *datumtype = InvalidOid; + } + + if (tupslot) + *tupslot = peraggstate->evalslot; + } + else + elog(ERROR, "AggSetSortInfo called on non ordered set function"); + } + + int + AggSetGetDistinctInfo(FunctionCallInfo fcinfo, + TupleTableSlot **uniqslot, + AttrNumber **sortColIdx, + FmgrInfo **equalfns) + { + if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData)) + { + AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context; + + if (uniqslot) + *uniqslot = peraggstate->uniqslot; + if (sortColIdx) + *sortColIdx = peraggstate->sortColIdx; + if (equalfns) + *equalfns = peraggstate->equalfns; + + return peraggstate->numDistinctCols; + } + else + elog(ERROR, "AggSetGetDistinctOperators called on non ordered set function"); + } + + int + AggSetGetSortOperators(FunctionCallInfo fcinfo, + AttrNumber **sortColIdx, + Oid **sortOperators, + Oid **sortEqOperators, + Oid **sortCollations, + bool **sortNullsFirst) + { + if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData)) + { + AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context; + + if (sortColIdx) + *sortColIdx = peraggstate->sortColIdx; + if (sortOperators) + *sortOperators = peraggstate->sortOperators; + if (sortEqOperators) + *sortEqOperators = peraggstate->sortEqOperators; + if (sortCollations) + *sortCollations = peraggstate->sortCollations; + if (sortNullsFirst) + *sortNullsFirst = peraggstate->sortNullsFirst; + + return peraggstate->numSortCols; + } + else + elog(ERROR, "AggSetGetSortOperators called on non ordered set function"); + } + + void + AggSetGetPerTupleContext(FunctionCallInfo fcinfo, + MemoryContext *memcontext) + { + if (fcinfo->context && IsA(fcinfo->context, AggStatePerAggData)) + { + AggStatePerAggData *peraggstate = (AggStatePerAggData *) fcinfo->context; + *memcontext = peraggstate->aggstate->tmpcontext->ecxt_per_tuple_memory; + } + } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 1139,1147 **** _copyAggref(const Aggref *from) --- 1139,1150 ---- COPY_NODE_FIELD(args); COPY_NODE_FIELD(aggorder); COPY_NODE_FIELD(aggdistinct); + COPY_NODE_FIELD(orddirectargs); COPY_NODE_FIELD(aggfilter); COPY_SCALAR_FIELD(aggstar); COPY_SCALAR_FIELD(aggvariadic); + COPY_SCALAR_FIELD(isordset); + COPY_SCALAR_FIELD(ishypothetical); COPY_SCALAR_FIELD(agglevelsup); COPY_LOCATION_FIELD(location); *************** *** 2174,2179 **** _copyFuncCall(const FuncCall *from) --- 2177,2183 ---- COPY_SCALAR_FIELD(agg_star); COPY_SCALAR_FIELD(agg_distinct); COPY_SCALAR_FIELD(func_variadic); + COPY_SCALAR_FIELD(has_within_group); COPY_NODE_FIELD(over); COPY_LOCATION_FIELD(location); *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 196,204 **** _equalAggref(const Aggref *a, const Aggref *b) --- 196,207 ---- COMPARE_NODE_FIELD(args); COMPARE_NODE_FIELD(aggorder); COMPARE_NODE_FIELD(aggdistinct); + COMPARE_NODE_FIELD(orddirectargs); COMPARE_NODE_FIELD(aggfilter); COMPARE_SCALAR_FIELD(aggstar); COMPARE_SCALAR_FIELD(aggvariadic); + COMPARE_SCALAR_FIELD(isordset); + COMPARE_SCALAR_FIELD(ishypothetical); COMPARE_SCALAR_FIELD(agglevelsup); COMPARE_LOCATION_FIELD(location); *************** *** 2006,2011 **** _equalFuncCall(const FuncCall *a, const FuncCall *b) --- 2009,2015 ---- COMPARE_SCALAR_FIELD(agg_star); COMPARE_SCALAR_FIELD(agg_distinct); COMPARE_SCALAR_FIELD(func_variadic); + COMPARE_SCALAR_FIELD(has_within_group); COMPARE_NODE_FIELD(over); COMPARE_LOCATION_FIELD(location); *** a/src/backend/nodes/makefuncs.c --- b/src/backend/nodes/makefuncs.c *************** *** 558,563 **** makeFuncCall(List *name, List *args, int location) --- 558,564 ---- n->agg_star = FALSE; n->agg_distinct = FALSE; n->func_variadic = FALSE; + n->has_within_group = FALSE; n->over = NULL; return n; } *** a/src/backend/nodes/nodeFuncs.c --- b/src/backend/nodes/nodeFuncs.c *************** *** 1631,1636 **** expression_tree_walker(Node *node, --- 1631,1641 ---- if (expression_tree_walker((Node *) expr->aggdistinct, walker, context)) return true; + + if (expression_tree_walker((Node *) expr->orddirectargs, + walker, context)) + return true; + if (walker((Node *) expr->aggfilter, context)) return true; } *************** *** 2155,2161 **** expression_tree_mutator(Node *node, --- 2160,2168 ---- MUTATE(newnode->args, aggref->args, List *); MUTATE(newnode->aggorder, aggref->aggorder, List *); MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *); + MUTATE(newnode->orddirectargs, aggref->orddirectargs, List *); MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *); + return (Node *) newnode; } break; *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 960,968 **** _outAggref(StringInfo str, const Aggref *node) --- 960,971 ---- WRITE_NODE_FIELD(args); WRITE_NODE_FIELD(aggorder); WRITE_NODE_FIELD(aggdistinct); + WRITE_NODE_FIELD(orddirectargs); WRITE_NODE_FIELD(aggfilter); WRITE_BOOL_FIELD(aggstar); WRITE_BOOL_FIELD(aggvariadic); + WRITE_BOOL_FIELD(isordset); + WRITE_BOOL_FIELD(ishypothetical); WRITE_UINT_FIELD(agglevelsup); WRITE_LOCATION_FIELD(location); } *************** *** 2090,2095 **** _outFuncCall(StringInfo str, const FuncCall *node) --- 2093,2099 ---- WRITE_BOOL_FIELD(agg_star); WRITE_BOOL_FIELD(agg_distinct); WRITE_BOOL_FIELD(func_variadic); + WRITE_BOOL_FIELD(has_within_group); WRITE_NODE_FIELD(over); WRITE_LOCATION_FIELD(location); } *** a/src/backend/nodes/readfuncs.c --- b/src/backend/nodes/readfuncs.c *************** *** 495,503 **** _readAggref(void) --- 495,506 ---- READ_NODE_FIELD(args); READ_NODE_FIELD(aggorder); READ_NODE_FIELD(aggdistinct); + READ_NODE_FIELD(orddirectargs); READ_NODE_FIELD(aggfilter); READ_BOOL_FIELD(aggstar); READ_BOOL_FIELD(aggvariadic); + READ_BOOL_FIELD(isordset); + READ_BOOL_FIELD(ishypothetical); READ_UINT_FIELD(agglevelsup); READ_LOCATION_FIELD(location); *** a/src/backend/optimizer/util/clauses.c --- b/src/backend/optimizer/util/clauses.c *************** *** 39,44 **** --- 39,45 ---- #include "parser/analyze.h" #include "parser/parse_coerce.h" #include "parser/parse_func.h" + #include "parser/parse_agg.h" #include "rewrite/rewriteManip.h" #include "tcop/tcopprot.h" #include "utils/acl.h" *************** *** 464,470 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context) QualCost argcosts; Oid *inputTypes; int numArguments; - ListCell *l; Assert(aggref->agglevelsup == 0); --- 465,470 ---- *************** *** 486,492 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context) costs->numOrderedAggs++; /* add component function execution costs to appropriate totals */ ! costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost; if (OidIsValid(aggfinalfn)) costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost; --- 486,493 ---- costs->numOrderedAggs++; /* add component function execution costs to appropriate totals */ ! if (OidIsValid(aggtransfn)) ! costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost; if (OidIsValid(aggfinalfn)) costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost; *************** *** 504,575 **** count_agg_clauses_walker(Node *node, count_agg_clauses_context *context) costs->transCost.startup += argcosts.startup; costs->transCost.per_tuple += argcosts.per_tuple; ! /* extract argument types (ignoring any ORDER BY expressions) */ ! inputTypes = (Oid *) palloc(sizeof(Oid) * list_length(aggref->args)); ! numArguments = 0; ! foreach(l, aggref->args) { ! TargetEntry *tle = (TargetEntry *) lfirst(l); ! if (!tle->resjunk) ! inputTypes[numArguments++] = exprType((Node *) tle->expr); ! } ! /* resolve actual type of transition state, if polymorphic */ ! if (IsPolymorphicType(aggtranstype)) ! { ! /* have to fetch the agg's declared input types... */ ! Oid *declaredArgTypes; ! int agg_nargs; ! ! (void) get_func_signature(aggref->aggfnoid, ! &declaredArgTypes, &agg_nargs); ! Assert(agg_nargs == numArguments); ! aggtranstype = enforce_generic_type_consistency(inputTypes, ! declaredArgTypes, ! agg_nargs, ! aggtranstype, ! false); ! pfree(declaredArgTypes); ! } ! /* ! * If the transition type is pass-by-value then it doesn't add ! * anything to the required size of the hashtable. If it is ! * pass-by-reference then we have to add the estimated size of the ! * value itself, plus palloc overhead. ! */ ! if (!get_typbyval(aggtranstype)) ! { ! int32 aggtranstypmod; ! int32 avgwidth; /* ! * If transition state is of same type as first input, assume it's ! * the same typmod (same width) as well. This works for cases ! * like MAX/MIN and is probably somewhat reasonable otherwise. */ ! if (numArguments > 0 && aggtranstype == inputTypes[0]) ! aggtranstypmod = exprTypmod((Node *) linitial(aggref->args)); ! else ! aggtranstypmod = -1; ! avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod); ! avgwidth = MAXALIGN(avgwidth); ! costs->transitionSpace += avgwidth + 2 * sizeof(void *); } ! else if (aggtranstype == INTERNALOID) { ! /* ! * INTERNAL transition type is a special case: although INTERNAL ! * is pass-by-value, it's almost certainly being used as a pointer ! * to some large data structure. We assume usage of ! * ALLOCSET_DEFAULT_INITSIZE, which is a good guess if the data is ! * being kept in a private memory context, as is done by ! * array_agg() for instance. ! */ ! costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE; } /* --- 505,595 ---- costs->transCost.startup += argcosts.startup; costs->transCost.per_tuple += argcosts.per_tuple; ! /* ! * If we're doing a sorted agg, we can punt the entire ! * determination of transition element size since we're not ! * going to be using it to determine hashtable limits. This ! * simplifies the code for hypothetical set functions. ! */ ! ! if (aggref->aggorder == NIL && aggref->aggdistinct == NIL) { ! Assert(!aggref->isordset); ! /* extract argument types (ignoring any ORDER BY expressions) */ ! inputTypes = (Oid *) palloc(sizeof(Oid) * FUNC_MAX_ARGS); ! numArguments = get_aggregate_argtypes(aggref, inputTypes, NULL); ! /* resolve actual type of transition state, if polymorphic */ ! if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype)) ! { ! /* have to fetch the agg's declared input types... */ ! Oid *declaredArgTypes; ! int agg_nargs; ! ! (void) get_func_signature(aggref->aggfnoid, ! &declaredArgTypes, &agg_nargs); ! ! /* ! * if variadic "any", might be more actual args than declared ! * args, but these extra args can't influence the determination ! * of polymorphic transition or result type. ! */ ! Assert(agg_nargs <= numArguments); ! ! aggtranstype = enforce_generic_type_consistency(inputTypes, ! declaredArgTypes, ! agg_nargs, ! aggtranstype, ! false); ! pfree(declaredArgTypes); ! } /* ! * If the transition type is pass-by-value then it doesn't add ! * anything to the required size of the hashtable. If it is ! * pass-by-reference then we have to add the estimated size of the ! * value itself, plus palloc overhead. */ ! if (OidIsValid(aggtranstype) && !get_typbyval(aggtranstype)) ! { ! int32 aggtranstypmod; ! int32 avgwidth; ! ! /* ! * If transition state is of same type as first input, assume it's ! * the same typmod (same width) as well. This works for cases ! * like MAX/MIN and is probably somewhat reasonable otherwise. ! */ ! if (numArguments > 0 && aggtranstype == inputTypes[0]) ! aggtranstypmod = exprTypmod((Node *) linitial(aggref->args)); ! else ! aggtranstypmod = -1; ! avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod); ! avgwidth = MAXALIGN(avgwidth); ! costs->transitionSpace += avgwidth + 2 * sizeof(void *); ! } ! else if (aggtranstype == INTERNALOID) ! { ! /* ! * INTERNAL transition type is a special case: although INTERNAL ! * is pass-by-value, it's almost certainly being used as a pointer ! * to some large data structure. We assume usage of ! * ALLOCSET_DEFAULT_INITSIZE, which is a good guess if the data is ! * being kept in a private memory context, as is done by ! * array_agg() for instance. ! */ ! costs->transitionSpace += ALLOCSET_DEFAULT_INITSIZE; ! } ! ! pfree(inputTypes); } ! else { ! costs->transitionSpace = work_mem; /* just in case */ } /* *************** *** 3826,3832 **** recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple) elog(ERROR, "function's resolved result type changed during planning"); /* perform any necessary typecasting of arguments */ ! make_fn_arguments(NULL, args, actual_arg_types, declared_arg_types); } /* --- 3846,3852 ---- elog(ERROR, "function's resolved result type changed during planning"); /* perform any necessary typecasting of arguments */ ! make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types, false); } /* *** a/src/backend/parser/analyze.c --- b/src/backend/parser/analyze.c *************** *** 951,957 **** transformSelectStmt(ParseState *pstate, SelectStmt *stmt) &qry->targetList, EXPR_KIND_ORDER_BY, true /* fix unknowns */ , ! false /* allow SQL92 rules */ ); qry->groupClause = transformGroupClause(pstate, stmt->groupClause, --- 951,958 ---- &qry->targetList, EXPR_KIND_ORDER_BY, true /* fix unknowns */ , ! false /* allow SQL92 rules */, ! false /* don't add duplicates */); qry->groupClause = transformGroupClause(pstate, stmt->groupClause, *************** *** 1211,1217 **** transformValuesClause(ParseState *pstate, SelectStmt *stmt) &qry->targetList, EXPR_KIND_ORDER_BY, true /* fix unknowns */ , ! false /* allow SQL92 rules */ ); qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, EXPR_KIND_OFFSET, "OFFSET"); --- 1212,1219 ---- &qry->targetList, EXPR_KIND_ORDER_BY, true /* fix unknowns */ , ! false /* allow SQL92 rules */, ! false /* don't add duplicates */ ); qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, EXPR_KIND_OFFSET, "OFFSET"); *************** *** 1435,1441 **** transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) &qry->targetList, EXPR_KIND_ORDER_BY, false /* no unknowns expected */ , ! false /* allow SQL92 rules */ ); /* restore namespace, remove jrte from rtable */ pstate->p_namespace = sv_namespace; --- 1437,1444 ---- &qry->targetList, EXPR_KIND_ORDER_BY, false /* no unknowns expected */ , ! false /* allow SQL92 rules */ , ! false /* don't add duplicates */ ); /* restore namespace, remove jrte from rtable */ pstate->p_namespace = sv_namespace; *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 494,499 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); --- 494,500 ---- %type opt_existing_window_name %type opt_if_not_exists %type filter_clause + %type within_group_clause /* * Non-keyword token types. These are hard-wired into the "flex" lexer. *************** *** 596,602 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING VERBOSE VERSION_P VIEW VOLATILE ! WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE XMLPI XMLROOT XMLSERIALIZE --- 597,603 ---- VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING VERBOSE VERSION_P VIEW VOLATILE ! WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE XMLPI XMLROOT XMLSERIALIZE *************** *** 3660,3666 **** AlterExtensionContentsStmt: n->action = $4; n->objtype = OBJECT_AGGREGATE; n->objname = $6; ! n->objargs = extractArgTypes($7); $$ = (Node *)n; } | ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')' --- 3661,3667 ---- n->action = $4; n->objtype = OBJECT_AGGREGATE; n->objname = $6; ! n->objargs = extractArgTypes(linitial($7)); $$ = (Node *)n; } | ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')' *************** *** 5239,5245 **** CommentStmt: CommentStmt *n = makeNode(CommentStmt); n->objtype = OBJECT_AGGREGATE; n->objname = $4; ! n->objargs = extractArgTypes($5); n->comment = $7; $$ = (Node *) n; } --- 5240,5246 ---- CommentStmt *n = makeNode(CommentStmt); n->objtype = OBJECT_AGGREGATE; n->objname = $4; ! n->objargs = extractArgTypes(linitial($5)); n->comment = $7; $$ = (Node *) n; } *************** *** 5405,5411 **** SecLabelStmt: n->provider = $3; n->objtype = OBJECT_AGGREGATE; n->objname = $6; ! n->objargs = extractArgTypes($7); n->label = $9; $$ = (Node *) n; } --- 5406,5412 ---- n->provider = $3; n->objtype = OBJECT_AGGREGATE; n->objname = $6; ! n->objargs = extractArgTypes(linitial($7)); n->label = $9; $$ = (Node *) n; } *************** *** 6405,6413 **** aggr_arg: func_arg } ; ! /* Zero-argument aggregates are named with * for consistency with COUNT(*) */ ! aggr_args: '(' aggr_args_list ')' { $$ = $2; } ! | '(' '*' ')' { $$ = NIL; } ; aggr_args_list: --- 6406,6458 ---- } ; ! /* ! * Aggregate args (for create aggregate, etc.) are treated as follows: ! * ! * (*) - no args ! * (func_arg,func_arg,...) - normal agg with args ! * () within group (func_arg,...) - ordered set func with no direct args ! * (func_arg,...) within group (func_arg,...) - ordered set func with args ! * (func_arg,...) within group (*) - ordered set func variadic special case ! * ! * This doesn't correspond to anything in the spec because the spec doesn't ! * have any DDL to create or modify ordered set functions, so we're winging ! * it here. ! * ! * Almost everything we do with an ordered set function treats its arguments ! * as though they were a single list, with the direct and grouped arg types ! * concatenated. So for simplicity, we construct a single list here. ! * ! * But we still need to know when creating an agg (but not for referring to it ! * later) where the division between direct and ordered args is; so this ! * production returns a pair (arglist,num) where num is the number of direct ! * args, or -1 if no within group clause was used. Most users of aggr_args, ! * other than CREATE AGGREGATE, therefore only need to pay attention to ! * linitial($n). ! */ ! ! aggr_args: '(' '*' ')' ! { ! $$ = list_make2(NIL, makeInteger(-1)); ! } ! | '(' aggr_args_list ')' ! { ! $$ = list_make2($2, makeInteger(-1)); ! } ! | '(' ')' WITHIN GROUP_P '(' aggr_args_list ')' ! { ! $$ = list_make2($6, makeInteger(0)); ! } ! | '(' aggr_args_list ')' WITHIN GROUP_P '(' aggr_args_list ')' ! { ! int n = list_length($2); ! $$ = list_make2(list_concat($2,$7), makeInteger(n)); ! } ! | '(' aggr_args_list ')' WITHIN GROUP_P '(' '*' ')' ! { ! int n = list_length($2); ! $$ = list_make2($2, makeInteger(n)); ! } ; aggr_args_list: *************** *** 6613,6619 **** RemoveAggrStmt: DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_AGGREGATE; n->objects = list_make1($3); ! n->arguments = list_make1(extractArgTypes($4)); n->behavior = $5; n->missing_ok = false; n->concurrent = false; --- 6658,6664 ---- DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_AGGREGATE; n->objects = list_make1($3); ! n->arguments = list_make1(extractArgTypes(linitial($4))); n->behavior = $5; n->missing_ok = false; n->concurrent = false; *************** *** 6624,6630 **** RemoveAggrStmt: DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_AGGREGATE; n->objects = list_make1($5); ! n->arguments = list_make1(extractArgTypes($6)); n->behavior = $7; n->missing_ok = true; n->concurrent = false; --- 6669,6675 ---- DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_AGGREGATE; n->objects = list_make1($5); ! n->arguments = list_make1(extractArgTypes(linitial($6))); n->behavior = $7; n->missing_ok = true; n->concurrent = false; *************** *** 6840,6846 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name RenameStmt *n = makeNode(RenameStmt); n->renameType = OBJECT_AGGREGATE; n->object = $3; ! n->objarg = extractArgTypes($4); n->newname = $7; n->missing_ok = false; $$ = (Node *)n; --- 6885,6891 ---- RenameStmt *n = makeNode(RenameStmt); n->renameType = OBJECT_AGGREGATE; n->object = $3; ! n->objarg = extractArgTypes(linitial($4)); n->newname = $7; n->missing_ok = false; $$ = (Node *)n; *************** *** 7314,7320 **** AlterObjectSchemaStmt: AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); n->objectType = OBJECT_AGGREGATE; n->object = $3; ! n->objarg = extractArgTypes($4); n->newschema = $7; n->missing_ok = false; $$ = (Node *)n; --- 7359,7365 ---- AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); n->objectType = OBJECT_AGGREGATE; n->object = $3; ! n->objarg = extractArgTypes(linitial($4)); n->newschema = $7; n->missing_ok = false; $$ = (Node *)n; *************** *** 7543,7549 **** AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId AlterOwnerStmt *n = makeNode(AlterOwnerStmt); n->objectType = OBJECT_AGGREGATE; n->object = $3; ! n->objarg = extractArgTypes($4); n->newowner = $7; $$ = (Node *)n; } --- 7588,7594 ---- AlterOwnerStmt *n = makeNode(AlterOwnerStmt); n->objectType = OBJECT_AGGREGATE; n->object = $3; ! n->objarg = extractArgTypes(linitial($4)); n->newowner = $7; $$ = (Node *)n; } *************** *** 9431,9436 **** sortby: a_expr USING qual_all_Op opt_nulls_order --- 9476,9486 ---- ; + within_group_clause: + WITHIN GROUP_P '(' sort_clause ')' { $$ = $4; } + | /*EMPTY*/ { $$ = NIL; } + ; + select_limit: limit_clause offset_clause { $$ = list_make2($2, $1); } | offset_clause limit_clause { $$ = list_make2($1, $2); } *************** *** 11152,11163 **** func_application: func_name '(' ')' * (Note that many of the special SQL functions wouldn't actually make any * sense as functional index entries, but we ignore that consideration here.) */ ! func_expr: func_application filter_clause over_clause { ! FuncCall *n = (FuncCall*)$1; ! n->agg_filter = $2; ! n->over = $3; ! $$ = (Node*)n; } | func_expr_common_subexpr { $$ = $1; } --- 11202,11236 ---- * (Note that many of the special SQL functions wouldn't actually make any * sense as functional index entries, but we ignore that consideration here.) */ ! func_expr: func_application within_group_clause filter_clause over_clause { ! FuncCall *n = (FuncCall *) $1; ! /* ! * the order clause for WITHIN GROUP and the one ! * for aggregate ORDER BY share a field, so we ! * have to check here that at most one is present. ! * We check for DISTINCT here to give a better ! * error position. Other consistency checks are ! * deferred to parse_func.c or parse_agg.c ! */ ! if ($2 != NIL) ! { ! if (n->agg_order != NIL) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("Cannot have multiple ORDER BY clauses with WITHIN GROUP"), ! parser_errposition(@2))); ! if (n->agg_distinct) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("Cannot have DISTINCT and WITHIN GROUP together"), ! parser_errposition(@2))); ! n->agg_order = $2; ! n->has_within_group = TRUE; ! } ! n->agg_filter = $3; ! n->over = $4; ! $$ = (Node *) n; } | func_expr_common_subexpr { $$ = $1; } *************** *** 12716,12721 **** unreserved_keyword: --- 12789,12795 ---- | VIEW | VOLATILE | WHITESPACE_P + | WITHIN | WITHOUT | WORK | WRAPPER *** a/src/backend/parser/parse_agg.c --- b/src/backend/parser/parse_agg.c *************** *** 44,50 **** typedef struct int sublevels_up; } check_ungrouped_columns_context; ! static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter); static bool check_agg_arguments_walker(Node *node, check_agg_arguments_context *context); static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry, --- 44,52 ---- int sublevels_up; } check_ungrouped_columns_context; ! static int check_agg_arguments(ParseState *pstate, ! List *args, ! List *agg_ordset, Expr *filter); static bool check_agg_arguments_walker(Node *node, check_agg_arguments_context *context); static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry, *************** *** 75,81 **** static bool check_ungrouped_columns_walker(Node *node, */ void transformAggregateCall(ParseState *pstate, Aggref *agg, ! List *args, List *aggorder, bool agg_distinct) { List *tlist; List *torder; --- 77,84 ---- */ void transformAggregateCall(ParseState *pstate, Aggref *agg, ! List *args, List *aggorder, ! bool agg_distinct, bool agg_within_group) { List *tlist; List *torder; *************** *** 93,104 **** transformAggregateCall(ParseState *pstate, Aggref *agg, */ tlist = NIL; attno = 1; ! foreach(lc, args) { ! Expr *arg = (Expr *) lfirst(lc); ! TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false); ! tlist = lappend(tlist, tle); } /* --- 96,119 ---- */ tlist = NIL; attno = 1; ! ! if (agg_within_group) { ! agg->isordset = TRUE; ! agg->orddirectargs = args; ! } ! else ! { ! foreach(lc, args) ! { ! Expr *arg = (Expr *) lfirst(lc); ! TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false); ! tlist = lappend(tlist, tle); ! } ! ! agg->isordset = FALSE; ! agg->orddirectargs = NIL; } /* *************** *** 109,114 **** transformAggregateCall(ParseState *pstate, Aggref *agg, --- 124,134 ---- * * We need to mess with p_next_resno since it will be used to number any * new targetlist entries. + * + * If and only if we're doing a WITHIN GROUP list, we preserve any + * duplicate expressions in the sort clause. This is needed because the + * sort clause of WITHIN GROUP is really an argument list, and we must + * keep the number and content of entries matching the specified input. */ save_next_resno = pstate->p_next_resno; pstate->p_next_resno = attno; *************** *** 118,124 **** transformAggregateCall(ParseState *pstate, Aggref *agg, &tlist, EXPR_KIND_ORDER_BY, true /* fix unknowns */ , ! true /* force SQL99 rules */ ); /* * If we have DISTINCT, transform that to produce a distinctList. --- 138,145 ---- &tlist, EXPR_KIND_ORDER_BY, true /* fix unknowns */ , ! true /* force SQL99 rules */ , ! agg_within_group /* keep duplicates? */ ); /* * If we have DISTINCT, transform that to produce a distinctList. *************** *** 160,166 **** transformAggregateCall(ParseState *pstate, Aggref *agg, * Check the arguments to compute the aggregate's level and detect * improper nesting. */ ! min_varlevel = check_agg_arguments(pstate, agg->args, agg->aggfilter); agg->agglevelsup = min_varlevel; /* Mark the correct pstate level as having aggregates */ --- 181,188 ---- * Check the arguments to compute the aggregate's level and detect * improper nesting. */ ! min_varlevel = check_agg_arguments(pstate, ! agg->args, agg->orddirectargs, agg->aggfilter); agg->agglevelsup = min_varlevel; /* Mark the correct pstate level as having aggregates */ *************** *** 312,318 **** transformAggregateCall(ParseState *pstate, Aggref *agg, * which we can't know until we finish scanning the arguments. */ static int ! check_agg_arguments(ParseState *pstate, List *args, Expr *filter) { int agglevel; check_agg_arguments_context context; --- 334,340 ---- * which we can't know until we finish scanning the arguments. */ static int ! check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter) { int agglevel; check_agg_arguments_context context; *************** *** 330,335 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter) --- 352,360 ---- check_agg_arguments_walker, (void *) &context); + (void) expression_tree_walker((Node *) agg_ordset, check_agg_arguments_walker, + (void *) &context); + /* * If we found no vars nor aggs at all, it's a level-zero aggregate; * otherwise, its level is the minimum of vars or aggs. *************** *** 353,360 **** check_agg_arguments(ParseState *pstate, List *args, Expr *filter) (errcode(ERRCODE_GROUPING_ERROR), errmsg("aggregate function calls cannot be nested"), parser_errposition(pstate, ! locate_agg_of_level((Node *) args, ! agglevel)))); return agglevel; } --- 378,385 ---- (errcode(ERRCODE_GROUPING_ERROR), errmsg("aggregate function calls cannot be nested"), parser_errposition(pstate, ! locate_agg_of_level((Node *) args, ! agglevel)))); return agglevel; } *************** *** 823,830 **** check_ungrouped_columns_walker(Node *node, * We do need to look at aggregates of lower levels, however. */ if (IsA(node, Aggref) && ! (int) ((Aggref *) node)->agglevelsup >= context->sublevels_up) return false; /* * If we have any GROUP BY items that are not simple Vars, check to see if --- 848,863 ---- * We do need to look at aggregates of lower levels, however. */ if (IsA(node, Aggref) && ! (int) ((Aggref *) node)->agglevelsup > context->sublevels_up) ! { return false; + } + else if (IsA(node, Aggref) && + (int) ((Aggref *) node)->agglevelsup == context->sublevels_up) + { + return check_ungrouped_columns_walker((Node*)(((Aggref *)node)->orddirectargs), + context); + } /* * If we have any GROUP BY items that are not simple Vars, check to see if *************** *** 1042,1044 **** build_aggregate_fnexprs(Oid *agg_input_types, --- 1075,1172 ---- agg_input_collation, COERCE_EXPLICIT_CALL); } + + void + build_orderedset_fnexprs(Oid *agg_input_types, + int agg_num_inputs, + bool agg_variadic, + Oid agg_result_type, + Oid agg_input_collation, + Oid *agg_input_collation_array, + Oid finalfn_oid, + Expr **finalfnexpr) + { + FuncExpr *fexpr; + Param *argp; + List *args = NIL; + int i = 0; + + /* + * Build arg list to use in the finalfn FuncExpr node. We really only care + * that finalfn can discover the actual argument types at runtime using + * get_fn_expr_argtype(), so it's okay to use Param nodes that don't + * correspond to any real Param. + */ + for (i = 0; i < agg_num_inputs; i++) + { + argp = makeNode(Param); + argp->paramkind = PARAM_EXEC; + argp->paramid = -1; + argp->paramtype = agg_input_types[i]; + argp->paramtypmod = -1; + argp->paramcollid = agg_input_collation_array[i]; + argp->location = -1; + + args = lappend(args, argp); + } + + fexpr = makeFuncExpr(finalfn_oid, + agg_result_type, + args, + InvalidOid, + agg_input_collation, + COERCE_EXPLICIT_CALL); + fexpr->funcvariadic = agg_variadic; + *finalfnexpr = (Expr *) fexpr; + } + + int get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes, Oid *inputCollations) + { + int numArguments = 0; + ListCell *lc; + + if (!(aggref->isordset)) + { + foreach(lc, aggref->args) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + if (!tle->resjunk) + { + inputTypes[numArguments] = exprType((Node *) tle->expr); + if (inputCollations != NULL) + inputCollations[numArguments] = exprCollation((Node *) tle->expr); + ++numArguments; + } + } + } + else + { + foreach(lc, aggref->orddirectargs) + { + Node *expr_orddirectargs = lfirst(lc); + + inputTypes[numArguments] = exprType(expr_orddirectargs); + if (inputCollations != NULL) + inputCollations[numArguments] = exprCollation(expr_orddirectargs); + + ++numArguments; + } + + if (!(aggref->ishypothetical)) + { + foreach(lc, aggref->args) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + inputTypes[numArguments] = exprType((Node *) tle->expr); + if (inputCollations != NULL) + inputCollations[numArguments] = exprCollation((Node *) tle->expr); + + ++numArguments; + } + } + } + + return numArguments; + } *** a/src/backend/parser/parse_clause.c --- b/src/backend/parser/parse_clause.c *************** *** 71,77 **** static void checkExprIsVarFree(ParseState *pstate, Node *n, static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist, ParseExprKind exprKind); static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node, ! List **tlist, ParseExprKind exprKind); static int get_matching_location(int sortgroupref, List *sortgrouprefs, List *exprs); static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle, --- 71,78 ---- static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist, ParseExprKind exprKind); static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node, ! List **tlist, ParseExprKind exprKind, ! bool keepDuplicates); static int get_matching_location(int sortgroupref, List *sortgrouprefs, List *exprs); static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle, *************** *** 1476,1482 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist, /* * Otherwise, we have an expression, so process it per SQL99 rules. */ ! return findTargetlistEntrySQL99(pstate, node, tlist, exprKind); } /* --- 1477,1483 ---- /* * Otherwise, we have an expression, so process it per SQL99 rules. */ ! return findTargetlistEntrySQL99(pstate, node, tlist, exprKind, false); } /* *************** *** 1491,1500 **** findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist, * node the ORDER BY, GROUP BY, etc expression to be matched * tlist the target list (passed by reference so we can append to it) * exprKind identifies clause type being processed */ static TargetEntry * findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist, ! ParseExprKind exprKind) { TargetEntry *target_result; ListCell *tl; --- 1492,1502 ---- * node the ORDER BY, GROUP BY, etc expression to be matched * tlist the target list (passed by reference so we can append to it) * exprKind identifies clause type being processed + * keepDuplicates if true, don't try and match to any existing entry */ static TargetEntry * findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist, ! ParseExprKind exprKind, bool keepDuplicates) { TargetEntry *target_result; ListCell *tl; *************** *** 1509,1532 **** findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist, */ expr = transformExpr(pstate, node, exprKind); ! foreach(tl, *tlist) { ! TargetEntry *tle = (TargetEntry *) lfirst(tl); ! Node *texpr; ! /* ! * Ignore any implicit cast on the existing tlist expression. ! * ! * This essentially allows the ORDER/GROUP/etc item to adopt the same ! * datatype previously selected for a textually-equivalent tlist item. ! * There can't be any implicit cast at top level in an ordinary SELECT ! * tlist at this stage, but the case does arise with ORDER BY in an ! * aggregate function. ! */ ! texpr = strip_implicit_coercions((Node *) tle->expr); ! if (equal(expr, texpr)) ! return tle; } /* --- 1511,1537 ---- */ expr = transformExpr(pstate, node, exprKind); ! if (!keepDuplicates) { ! foreach(tl, *tlist) ! { ! TargetEntry *tle = (TargetEntry *) lfirst(tl); ! Node *texpr; ! /* ! * Ignore any implicit cast on the existing tlist expression. ! * ! * This essentially allows the ORDER/GROUP/etc item to adopt the same ! * datatype previously selected for a textually-equivalent tlist item. ! * There can't be any implicit cast at top level in an ordinary SELECT ! * tlist at this stage, but the case does arise with ORDER BY in an ! * aggregate function. ! */ ! texpr = strip_implicit_coercions((Node *) tle->expr); ! if (equal(expr, texpr)) ! return tle; ! } } /* *************** *** 1568,1574 **** transformGroupClause(ParseState *pstate, List *grouplist, if (useSQL99) tle = findTargetlistEntrySQL99(pstate, gexpr, ! targetlist, exprKind); else tle = findTargetlistEntrySQL92(pstate, gexpr, targetlist, exprKind); --- 1573,1579 ---- if (useSQL99) tle = findTargetlistEntrySQL99(pstate, gexpr, ! targetlist, exprKind, false); else tle = findTargetlistEntrySQL92(pstate, gexpr, targetlist, exprKind); *************** *** 1635,1645 **** transformSortClause(ParseState *pstate, List **targetlist, ParseExprKind exprKind, bool resolveUnknown, ! bool useSQL99) { List *sortlist = NIL; ListCell *olitem; foreach(olitem, orderlist) { SortBy *sortby = (SortBy *) lfirst(olitem); --- 1640,1653 ---- List **targetlist, ParseExprKind exprKind, bool resolveUnknown, ! bool useSQL99, ! bool keepDuplicates) { List *sortlist = NIL; ListCell *olitem; + Assert(useSQL99 || !keepDuplicates); + foreach(olitem, orderlist) { SortBy *sortby = (SortBy *) lfirst(olitem); *************** *** 1647,1653 **** transformSortClause(ParseState *pstate, if (useSQL99) tle = findTargetlistEntrySQL99(pstate, sortby->node, ! targetlist, exprKind); else tle = findTargetlistEntrySQL92(pstate, sortby->node, targetlist, exprKind); --- 1655,1661 ---- if (useSQL99) tle = findTargetlistEntrySQL99(pstate, sortby->node, ! targetlist, exprKind, keepDuplicates); else tle = findTargetlistEntrySQL92(pstate, sortby->node, targetlist, exprKind); *************** *** 1717,1723 **** transformWindowDefinitions(ParseState *pstate, targetlist, EXPR_KIND_WINDOW_ORDER, true /* fix unknowns */ , ! true /* force SQL99 rules */ ); partitionClause = transformGroupClause(pstate, windef->partitionClause, targetlist, --- 1725,1732 ---- targetlist, EXPR_KIND_WINDOW_ORDER, true /* fix unknowns */ , ! true /* force SQL99 rules */, ! false /* don't add duplicates */); partitionClause = transformGroupClause(pstate, windef->partitionClause, targetlist, *** a/src/backend/parser/parse_collate.c --- b/src/backend/parser/parse_collate.c *************** *** 73,79 **** typedef struct static bool assign_query_collations_walker(Node *node, ParseState *pstate); static bool assign_collations_walker(Node *node, assign_collations_context *context); ! /* * assign_query_collations() --- 73,81 ---- static bool assign_query_collations_walker(Node *node, ParseState *pstate); static bool assign_collations_walker(Node *node, assign_collations_context *context); ! static void assign_aggregate_collations(Aggref *aggref, ! assign_collations_context *context, ! assign_collations_context *loccontext); /* * assign_query_collations() *************** *** 564,607 **** assign_collations_walker(Node *node, assign_collations_context *context) case T_Aggref: { /* ! * Aggref is a special case because expressions ! * used only for ordering shouldn't be taken to ! * conflict with each other or with regular args. ! * So we apply assign_expr_collations() to them ! * rather than passing down our loccontext. ! * ! * Note that we recurse to each TargetEntry, not ! * directly to its contained expression, so that ! * the case above for T_TargetEntry will apply ! * appropriate checks to agg ORDER BY items. ! * ! * Likewise, we assign collations for the (bool) ! * expression in aggfilter, independently of any ! * other args. ! * ! * We need not recurse into the aggorder or ! * aggdistinct lists, because those contain only ! * SortGroupClause nodes which we need not ! * process. */ Aggref *aggref = (Aggref *) node; - ListCell *lc; ! foreach(lc, aggref->args) ! { ! TargetEntry *tle = (TargetEntry *) lfirst(lc); ! ! Assert(IsA(tle, TargetEntry)); ! if (tle->resjunk) ! assign_expr_collations(context->pstate, ! (Node *) tle); ! else ! (void) assign_collations_walker((Node *) tle, ! &loccontext); ! } assign_expr_collations(context->pstate, ! (Node *) aggref->aggfilter); } break; case T_WindowFunc: --- 566,581 ---- case T_Aggref: { /* ! * Aggref is special enough that we give it its own ! * function. The FILTER clause is independent of the ! * rest of the aggregate, however. */ Aggref *aggref = (Aggref *) node; ! assign_aggregate_collations(aggref, context, &loccontext); assign_expr_collations(context->pstate, ! (Node *) aggref->aggfilter); } break; case T_WindowFunc: *************** *** 802,804 **** assign_collations_walker(Node *node, assign_collations_context *context) --- 776,934 ---- return false; } + + + /* + * Aggref is a special case because expressions used only for ordering + * shouldn't be taken to conflict with each other or with regular args. So we + * apply assign_expr_collations() to them rather than passing down our + * loccontext. + * + * Note that we recurse to each TargetEntry, not directly to its contained + * expression, so that the case above for T_TargetEntry will apply appropriate + * checks to agg ORDER BY items. + * + * We need not recurse into the aggorder or aggdistinct lists, because those + * contain only SortGroupClause nodes which we need not process. + * + * For ordered set functions, it's unfortunately unclear how best to proceed. + * The spec-defined inverse distribution functions have only one sort column + * and don't allow collatable types, but this is clearly unsatisfactory in the + * general case. Compromise by taking the sort column as part of the collation + * determination if, and only if, there is only one such column, and force the + * final choice of input collation down into the sort column if need be; but + * don't error out unless actually necessary (leaving it up to the function to + * handle the issue at runtime). This ugly wart is justified by the fact that + * there seems to be no other good way to get a result collation for + * percentile_* applied to a collatable type. + * + * But hypothetical set functions are special; they must have + * pairwise-assigned collations for each matching pair of args, and again we + * need to force the final choice of collation down into the sort column to + * ensure that the sort happens on the chosen collation. If there are any + * additional args (not allowed in the spec, but a user-defined function might + * have some), those contribute to the result collation in the normal way. + * (The hypothetical paired args never contribute to the result collation at + * all.) + */ + + static Expr * + relabel_expr_collation(Expr *expr, Oid newcollation) + { + RelabelType *node = makeNode(RelabelType); + node->arg = expr; + node->resulttype = exprType((Node *)expr); + node->resulttypmod = exprTypmod((Node *)expr); + node->resultcollid = newcollation; + node->relabelformat = COERCE_IMPLICIT_CAST; + node->location = exprLocation((Node *)expr); + return (Expr *) node; + } + + static void + assign_aggregate_collations(Aggref *aggref, + assign_collations_context *context, + assign_collations_context *loccontext) + { + ListCell *lc; + + if (aggref->ishypothetical) + { + /*- + * Hypothetical set function, i.e. + * func(..., a,b,c,...) within group (p,q,r,...) + * + * Any initial set of direct args (before "a") contributes to the + * result collation in the usual way for function args. But none of + * a,b,c... or p,q,r... contribute at all; instead, they must be + * paired up (as though UNIONed) and the sorted col's collation forced + * to the chosen value (so that we sort it correctly). + */ + int initial_args = list_length(aggref->orddirectargs) - list_length(aggref->args); + ListCell *h_arg = list_head(aggref->orddirectargs); + ListCell *s_arg = list_head(aggref->args); + + Assert(initial_args >= 0); + + while (initial_args-- > 0) + { + (void) assign_collations_walker((Node *) lfirst(h_arg), loccontext); + h_arg = lnext(h_arg); + } + + for_each_cell(h_arg,h_arg) + { + TargetEntry *tle = (TargetEntry *) lfirst(s_arg); + Oid coll = select_common_collation(context->pstate, + list_make2(lfirst(h_arg),lfirst(s_arg)), + false); + + /* + * we can only get InvalidOid here if the type is not collatable, + * so no need to try and relabel in that case. + */ + + if (OidIsValid(coll) + && coll != exprCollation((Node *)(tle->expr))) + { + tle->expr = relabel_expr_collation(tle->expr, coll); + } + + s_arg = lnext(s_arg); + } + } + else if (aggref->isordset && list_length(aggref->args) == 1) + { + /* + * Ordered set func with one sorted arg + */ + TargetEntry *tle = (TargetEntry *) linitial(aggref->args); + + /* do the TLE first so that it won't error out on conflicts */ + + (void) assign_collations_walker((Node *) tle, + loccontext); + + (void) assign_collations_walker((Node *) aggref->orddirectargs, + loccontext); + + /* + * If the sort col is a collatable type, and we chose a collation, + * and it's not the one the sort col already has, then force the + * sort col's collation (which can't have been explicit) to the + * chosen one. Otherwise leave it alone. + */ + if (type_is_collatable(exprType((Node *)(tle->expr))) + && (loccontext->strength == COLLATE_IMPLICIT + || loccontext->strength == COLLATE_EXPLICIT) + && exprCollation((Node *)(tle->expr)) != loccontext->collation) + { + tle->expr = relabel_expr_collation(tle->expr, loccontext->collation); + } + } + else + { + /* + * For this case, we do the direct args (if any) together, as is + * normal for functions, but args which are either used only for + * sorting or are only part of a WITHIN GROUP are processed + * individually. + */ + + (void) assign_collations_walker((Node *) aggref->orddirectargs, + loccontext); + + foreach(lc, aggref->args) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + Assert(IsA(tle, TargetEntry)); + if (tle->resjunk) + assign_expr_collations(context->pstate, + (Node *) tle); + else + (void) assign_collations_walker((Node *) tle, + loccontext); + } + } + } *** a/src/backend/parser/parse_expr.c --- b/src/backend/parser/parse_expr.c *************** *** 463,470 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection) newresult = ParseFuncOrColumn(pstate, list_make1(n), list_make1(result), ! NIL, NULL, false, false, false, ! NULL, true, location); if (newresult == NULL) unknown_attribute(pstate, result, strVal(n), location); result = newresult; --- 463,470 ---- newresult = ParseFuncOrColumn(pstate, list_make1(n), list_make1(result), ! location, ! NULL); if (newresult == NULL) unknown_attribute(pstate, result, strVal(n), location); result = newresult; *************** *** 631,638 **** transformColumnRef(ParseState *pstate, ColumnRef *cref) node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! NIL, NULL, false, false, false, ! NULL, true, cref->location); } break; } --- 631,637 ---- node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! cref->location, NULL); } break; } *************** *** 676,683 **** transformColumnRef(ParseState *pstate, ColumnRef *cref) node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! NIL, NULL, false, false, false, ! NULL, true, cref->location); } break; } --- 675,681 ---- node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! cref->location, NULL); } break; } *************** *** 734,741 **** transformColumnRef(ParseState *pstate, ColumnRef *cref) node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! NIL, NULL, false, false, false, ! NULL, true, cref->location); } break; } --- 732,738 ---- node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! cref->location, NULL); } break; } *************** *** 1242,1279 **** transformFuncCall(ParseState *pstate, FuncCall *fn) { List *targs; ListCell *args; - Expr *tagg_filter; /* Transform the list of arguments ... */ targs = NIL; foreach(args, fn->args) { ! targs = lappend(targs, transformExprRecurse(pstate, ! (Node *) lfirst(args))); } - /* - * Transform the aggregate filter using transformWhereClause(), to which - * FILTER is virtually identical... - */ - tagg_filter = NULL; - if (fn->agg_filter != NULL) - tagg_filter = (Expr *) - transformWhereClause(pstate, (Node *) fn->agg_filter, - EXPR_KIND_FILTER, "FILTER"); - /* ... and hand off to ParseFuncOrColumn */ return ParseFuncOrColumn(pstate, fn->funcname, targs, ! fn->agg_order, ! tagg_filter, ! fn->agg_star, ! fn->agg_distinct, ! fn->func_variadic, ! fn->over, ! false, ! fn->location); } static Node * --- 1239,1258 ---- { List *targs; ListCell *args; /* Transform the list of arguments ... */ targs = NIL; foreach(args, fn->args) { ! targs = lappend(targs, transformExprRecurse(pstate, (Node *) lfirst(args))); } /* ... and hand off to ParseFuncOrColumn */ return ParseFuncOrColumn(pstate, fn->funcname, targs, ! fn->location, ! fn); } static Node * *** a/src/backend/parser/parse_func.c --- b/src/backend/parser/parse_func.c *************** *** 17,32 **** --- 17,35 ---- #include "access/htup_details.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" + #include "catalog/pg_aggregate.h" #include "funcapi.h" #include "lib/stringinfo.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "parser/parse_agg.h" + #include "parser/parse_clause.h" #include "parser/parse_coerce.h" #include "parser/parse_func.h" #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "parser/parse_type.h" + #include "parser/parse_expr.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" *************** *** 56,70 **** static Node *ParseComplexProjection(ParseState *pstate, char *funcname, * Also, when is_column is true, we return NULL on failure rather than * reporting a no-such-function error. * ! * The argument expressions (in fargs) and filter must have been transformed ! * already. But the agg_order expressions, if any, have not been. */ Node * ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ! List *agg_order, Expr *agg_filter, ! bool agg_star, bool agg_distinct, bool func_variadic, ! WindowDef *over, bool is_column, int location) { Oid rettype; Oid funcid; ListCell *l; --- 59,79 ---- * Also, when is_column is true, we return NULL on failure rather than * reporting a no-such-function error. * ! * The argument expressions (in fargs) must have been transformed ! * already. */ Node * ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ! int location, FuncCall *fn) { + List *agg_order = (fn ? fn->agg_order : NIL); + Expr *agg_filter = NULL; + bool agg_star = (fn ? fn->agg_star : false); + bool agg_distinct = (fn ? fn->agg_distinct : false); + bool agg_within_group = (fn ? fn->has_within_group : false); + bool func_variadic = (fn ? fn->func_variadic : false); + WindowDef *over = (fn ? fn->over : NULL); + bool is_column = (fn == NULL); Oid rettype; Oid funcid; ListCell *l; *************** *** 81,86 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 90,101 ---- int nvargs; Oid vatype; FuncDetailCode fdresult; + int number_of_args = -1; + bool isordsetfunc = false; + bool ishypotheticalsetfunc = false; + + /* Check if the function has WITHIN GROUP as well as distinct. */ + Assert(!(agg_within_group && agg_distinct)); /* * Most of the rest of the parser just assumes that functions do not have *************** *** 98,103 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 113,127 ---- parser_errposition(pstate, location))); /* + * Transform the aggregate filter using transformWhereClause(), to which + * FILTER is virtually identical... + */ + if (fn && fn->agg_filter != NULL) + agg_filter = (Expr *) + transformWhereClause(pstate, (Node *) fn->agg_filter, + EXPR_KIND_FILTER, "FILTER"); + + /* * Extract arg type info in preparation for function lookup. * * If any arguments are Param markers of type VOID, we discard them from *************** *** 163,168 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 187,198 ---- } } + if (agg_within_group && argnames) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ordered set functions cannot use named arguments"), + parser_errposition(pstate, location))); + if (fargs) { first_arg = linitial(fargs); *************** *** 170,175 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 200,225 ---- } /* + * If WITHIN GROUP is present, we need to call transformExpr on each + * SortBy node in agg_order, then call exprType and append to + * actual_arg_types, in order to get the types of values in WITHIN GROUP + * clause. + */ + if (agg_within_group) + { + Assert(agg_order != NIL); + + foreach(l, agg_order) + { + SortBy *arg = (SortBy *) lfirst(l); + + arg->node = transformExpr(pstate, arg->node, EXPR_KIND_ORDER_BY); + + actual_arg_types[nargs++] = exprType(arg->node); + } + } + + /* * Check for column projection: if function has one argument, and that * argument is of complex type, and function name is not qualified, then * the "function call" could be a projection. We also check that there *************** *** 247,252 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 297,308 ---- errmsg("DISTINCT specified, but %s is not an aggregate function", NameListToString(funcname)), parser_errposition(pstate, location))); + if (agg_within_group) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("WITHIN GROUP specified, but %s is not an ordered set function", + NameListToString(funcname)), + parser_errposition(pstate, location))); if (agg_order != NIL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), *************** *** 266,271 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 322,373 ---- NameListToString(funcname)), parser_errposition(pstate, location))); } + else if (fdresult == FUNCDETAIL_AGGREGATE) + { + HeapTuple tup; + Form_pg_aggregate classForm; + + tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tup)) /* should not happen */ + elog(ERROR, "cache lookup failed for aggregate %u", funcid); + + classForm = (Form_pg_aggregate) GETSTRUCT(tup); + isordsetfunc = classForm->aggisordsetfunc; + + if (isordsetfunc) + { + if (classForm->aggordnargs == -2) + { + ishypotheticalsetfunc = true; + + if (nvargs != 2*list_length(agg_order)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("Incorrect number of arguments for hypothetical set function"), + parser_errposition(pstate, location))); + } + else + { + number_of_args = classForm->aggordnargs; + } + + if (!agg_within_group) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("WITHIN GROUP is required for call to ordered set function %s", + NameListToString(funcname)), + parser_errposition(pstate, location))); + + if (over) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("OVER clause not supported for call to ordered set function %s", + NameListToString(funcname)), + parser_errposition(pstate, location))); + } + + ReleaseSysCache(tup); + } else if (!(fdresult == FUNCDETAIL_AGGREGATE || fdresult == FUNCDETAIL_WINDOWFUNC)) { *************** *** 351,363 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, false); /* perform the necessary typecasting of arguments */ ! make_fn_arguments(pstate, fargs, actual_arg_types, declared_arg_types); /* * If it's a variadic function call, transform the last nvargs arguments * into an array --- unless it's an "any" variadic. */ ! if (nvargs > 0 && declared_arg_types[nargs - 1] != ANYOID) { ArrayExpr *newa = makeNode(ArrayExpr); int non_var_args = nargs - nvargs; --- 453,468 ---- false); /* perform the necessary typecasting of arguments */ ! make_fn_arguments(pstate, fargs, (isordsetfunc) ? agg_order : NIL, ! actual_arg_types, ! declared_arg_types, ! ishypotheticalsetfunc); /* * If it's a variadic function call, transform the last nvargs arguments * into an array --- unless it's an "any" variadic. */ ! if (nvargs > 0 && vatype != ANYOID) { ArrayExpr *newa = makeNode(ArrayExpr); int non_var_args = nargs - nvargs; *************** *** 388,403 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, * When function is called with an explicit VARIADIC labeled parameter, * and the declared_arg_type is "any", then sanity check the actual * parameter type now - it must be an array. */ if (nargs > 0 && vatype == ANYOID && func_variadic) { ! Oid va_arr_typid = actual_arg_types[nargs - 1]; if (!OidIsValid(get_element_type(va_arr_typid))) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("VARIADIC argument must be an array"), ! parser_errposition(pstate, exprLocation((Node *) llast(fargs))))); } /* build the appropriate output structure */ --- 493,523 ---- * When function is called with an explicit VARIADIC labeled parameter, * and the declared_arg_type is "any", then sanity check the actual * parameter type now - it must be an array. + * + * Also, it can't be a hypothetical set function, and if it's an ordered + * set function, the variadic labeled parameter is the last _direct_ arg, + * not an ordered arg. (In practice we're unlikely to get this far for + * hypotheticals, since make_fn_arguments would probably fail to unify + * types, but we can't change the order of these.) */ if (nargs > 0 && vatype == ANYOID && func_variadic) { ! int ignore_args = (agg_within_group ? list_length(agg_order) : 0); ! Oid va_arr_typid = actual_arg_types[nargs - 1 - ignore_args]; ! ! if (ishypotheticalsetfunc) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("explicit VARIADIC argument not allowed for hypothetical set function"), ! parser_errposition(pstate, ! exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args))))); if (!OidIsValid(get_element_type(va_arr_typid))) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("VARIADIC argument must be an array"), ! parser_errposition(pstate, ! exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args))))); } /* build the appropriate output structure */ *************** *** 421,426 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 541,552 ---- /* aggregate function */ Aggref *aggref = makeNode(Aggref); + if (agg_within_group && !isordsetfunc) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("%s is not an ordered set function", + func_signature_string(funcname, nargs, NIL, actual_arg_types)))); + aggref->aggfnoid = funcid; aggref->aggtype = rettype; /* aggcollid and inputcollid will be set by parse_collate.c */ *************** *** 428,441 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, aggref->aggfilter = agg_filter; aggref->aggstar = agg_star; aggref->aggvariadic = func_variadic; /* agglevelsup will be set by transformAggregateCall */ aggref->location = location; /* * Reject attempt to call a parameterless aggregate without (*) * syntax. This is mere pedantry but some folks insisted ... */ ! if (fargs == NIL && !agg_star) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s(*) must be used to call a parameterless aggregate function", --- 554,577 ---- aggref->aggfilter = agg_filter; aggref->aggstar = agg_star; aggref->aggvariadic = func_variadic; + aggref->ishypothetical = ishypotheticalsetfunc; /* agglevelsup will be set by transformAggregateCall */ aggref->location = location; + if (isordsetfunc + && number_of_args >= 0 + && number_of_args != list_length(fargs)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("Incorrect number of direct arguments to ordered set function %s", + NameListToString(funcname)), + parser_errposition(pstate, location))); + /* * Reject attempt to call a parameterless aggregate without (*) * syntax. This is mere pedantry but some folks insisted ... */ ! if (fargs == NIL && !agg_star && !agg_within_group) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s(*) must be used to call a parameterless aggregate function", *************** *** 464,470 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, parser_errposition(pstate, location))); /* parse_agg.c does additional aggregate-specific processing */ ! transformAggregateCall(pstate, aggref, fargs, agg_order, agg_distinct); retval = (Node *) aggref; } --- 600,607 ---- parser_errposition(pstate, location))); /* parse_agg.c does additional aggregate-specific processing */ ! transformAggregateCall(pstate, aggref, fargs, agg_order, ! agg_distinct, agg_within_group); retval = (Node *) aggref; } *************** *** 473,478 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 610,621 ---- /* window function */ WindowFunc *wfunc = makeNode(WindowFunc); + if (agg_within_group) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("WITHIN GROUP not allowed in window functions"), + parser_errposition(pstate, location))); + /* * True window functions must be called with a window definition. */ *************** *** 1374,1384 **** func_get_detail(List *funcname, void make_fn_arguments(ParseState *pstate, List *fargs, Oid *actual_arg_types, ! Oid *declared_arg_types) { ListCell *current_fargs; int i = 0; foreach(current_fargs, fargs) { --- 1517,1537 ---- void make_fn_arguments(ParseState *pstate, List *fargs, + List *agg_order, Oid *actual_arg_types, ! Oid *declared_arg_types, ! bool requiresUnification) { ListCell *current_fargs; + ListCell *current_aoargs; int i = 0; + int unify_offset = -1; + + if (requiresUnification) + { + unify_offset = list_length(fargs) - list_length(agg_order); + Assert(unify_offset >= 0); + } foreach(current_fargs, fargs) { *************** *** 1386,1391 **** make_fn_arguments(ParseState *pstate, --- 1539,1545 ---- if (actual_arg_types[i] != declared_arg_types[i]) { Node *node = (Node *) lfirst(current_fargs); + Node *temp = NULL; /* * If arg is a NamedArgExpr, coerce its input expr instead --- we *************** *** 1406,1423 **** make_fn_arguments(ParseState *pstate, } else { ! node = coerce_type(pstate, ! node, ! actual_arg_types[i], ! declared_arg_types[i], -1, ! COERCION_IMPLICIT, ! COERCE_IMPLICIT_CAST, ! -1); ! lfirst(current_fargs) = node; } } i++; } } /* --- 1560,1625 ---- } else { ! /* ! * If we are dealing with a hypothetical set function, we ! * need to unify agg_order and fargs. ! */ ! ! if (declared_arg_types[i] == ANYOID && requiresUnification) ! { ! Oid unification_oid; ! SortBy *unify_with = (SortBy *) list_nth(agg_order,i - unify_offset); ! ! unification_oid = select_common_type(pstate, ! list_make2(unify_with->node,node), ! "WITHIN GROUP", ! NULL); ! ! declared_arg_types[i + list_length(agg_order)] = unification_oid; ! ! temp = coerce_type(pstate, ! node, ! actual_arg_types[i], ! unification_oid, -1, ! COERCION_IMPLICIT, ! COERCE_IMPLICIT_CAST, ! -1); ! } ! else ! { ! temp = coerce_type(pstate, ! node, ! actual_arg_types[i], ! declared_arg_types[i], -1, ! COERCION_IMPLICIT, ! COERCE_IMPLICIT_CAST, ! -1); ! } ! ! lfirst(current_fargs) = temp; } } i++; } + + foreach(current_aoargs, agg_order) + { + if (actual_arg_types[i] != declared_arg_types[i]) + { + SortBy *node = (SortBy *) lfirst(current_aoargs); + Node *temp = NULL; + + temp = coerce_type(pstate, + node->node, + actual_arg_types[i], + declared_arg_types[i], -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, + -1); + node->node = temp; + } + i++; + } } /* *** a/src/backend/parser/parse_oper.c --- b/src/backend/parser/parse_oper.c *************** *** 823,829 **** make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, false); /* perform the necessary typecasting of arguments */ ! make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types); /* and build the expression node */ result = makeNode(OpExpr); --- 823,829 ---- false); /* perform the necessary typecasting of arguments */ ! make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false); /* and build the expression node */ result = makeNode(OpExpr); *************** *** 953,959 **** make_scalar_array_op(ParseState *pstate, List *opname, declared_arg_types[1] = res_atypeId; /* perform the necessary typecasting of arguments */ ! make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types); /* and build the expression node */ result = makeNode(ScalarArrayOpExpr); --- 953,959 ---- declared_arg_types[1] = res_atypeId; /* perform the necessary typecasting of arguments */ ! make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false); /* and build the expression node */ result = makeNode(ScalarArrayOpExpr); *** a/src/backend/utils/adt/Makefile --- b/src/backend/utils/adt/Makefile *************** *** 19,31 **** OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \ array_userfuncs.o arrayutils.o bool.o \ cash.o char.o date.o datetime.o datum.o domains.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 \ 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 \ ! ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \ ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \ tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \ tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \ --- 19,31 ---- array_userfuncs.o arrayutils.o bool.o \ cash.o char.o date.o datetime.o datum.o domains.o \ enum.o float.o format_type.o \ ! geo_ops.o geo_selfuncs.o hypotheticalset.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 \ 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 \ ! inversedistribution.o ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \ ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o trigfuncs.o \ tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \ tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \ *** /dev/null --- b/src/backend/utils/adt/hypotheticalset.c *************** *** 0 **** --- 1,219 ---- + /*------------------------------------------------------------------------- + * + * hypotheticalset.c + * Hypothetical set 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/hypotheticalset.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + #include "fmgr.h" + #include + #include + + #include "utils/tuplesort.h" + #include "catalog/pg_type.h" + #include "utils/datetime.h" + #include "utils/builtins.h" + #include "executor/executor.h" + + Datum hypothetical_rank_final(PG_FUNCTION_ARGS); + Datum hypothetical_dense_rank_final(PG_FUNCTION_ARGS); + Datum hypothetical_percent_rank_final(PG_FUNCTION_ARGS); + Datum hypothetical_cume_dist_final(PG_FUNCTION_ARGS); + + + /* + * 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; but we + * only need them once per call site. Store a pointer to the tupdesc as a + * sentinel. + */ + + static void + hypothetical_check_argtypes(FunctionCallInfo fcinfo, int nargs, TupleDesc tupdesc) + { + int i; + + if (!tupdesc + || (nargs + 1) != tupdesc->natts + || tupdesc->attrs[nargs]->atttypid != BOOLOID) + elog(ERROR, "type mismatch in hypothetical set function"); + + for (i = 0; i < nargs; ++i) + if (get_fn_expr_argtype(fcinfo->flinfo,i) != tupdesc->attrs[i]->atttypid) + elog(ERROR, "type mismatch in hypothetical set function"); + + fcinfo->flinfo->fn_extra = tupdesc; + } + + /* + * rank(float8) - rank of hypothetical row + */ + Datum + hypothetical_rank_final(PG_FUNCTION_ARGS) + { + Tuplesortstate *sorter = NULL; + TupleDesc tupdesc = NULL; + TupleTableSlot *slot = NULL; + Oid datumtype = InvalidOid; + int nargs = PG_NARGS(); + int i; + int64 rank = 1; + + AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype); + + if (fcinfo->flinfo->fn_extra == NULL + || fcinfo->flinfo->fn_extra != tupdesc) + hypothetical_check_argtypes(fcinfo, nargs, tupdesc); + + /* insert the hypothetical row into the sort */ + + ExecClearTuple(slot); + for (i = 0; i < nargs; ++i) + { + slot->tts_values[i] = PG_GETARG_DATUM(i); + slot->tts_isnull[i] = PG_ARGISNULL(i); + } + slot->tts_values[nargs] = BoolGetDatum(true); + slot->tts_isnull[nargs] = false; + ExecStoreVirtualTuple(slot); + + tuplesort_puttupleslot(sorter, slot); + + tuplesort_performsort(sorter); + + while (tuplesort_gettupleslot(sorter, true, slot)) + { + bool isnull; + Datum d = slot_getattr(slot, nargs + 1, &isnull); + + if (!isnull && DatumGetBool(d)) + break; + + ++rank; + } + + ExecClearTuple(slot); + + PG_RETURN_INT64(rank); + } + + /* + * dense_rank(float8) - rank of hypothetical row + * without gap in ranking + */ + Datum + hypothetical_dense_rank_final(PG_FUNCTION_ARGS) + { + Tuplesortstate *sorter = NULL; + TupleDesc tupdesc = NULL; + TupleTableSlot *slot = NULL; + Oid datumtype = InvalidOid; + int nargs = PG_NARGS(); + int i; + int64 rank = 1; + int duplicate_count = 0; + TupleTableSlot *slot2 = NULL; + AttrNumber *colidx; + FmgrInfo *equalfns; + int numDistinctCol = 0; + MemoryContext memcontext; + + AggSetGetSortInfo(fcinfo, &sorter, &tupdesc, &slot, &datumtype); + + if (fcinfo->flinfo->fn_extra == NULL + || fcinfo->flinfo->fn_extra != tupdesc) + hypothetical_check_argtypes(fcinfo, nargs, tupdesc); + + /* insert the hypothetical row into the sort */ + + ExecClearTuple(slot); + for (i = 0; i < nargs; ++i) + { + slot->tts_values[i] = PG_GETARG_DATUM(i); + slot->tts_isnull[i] = PG_ARGISNULL(i); + } + slot->tts_values[nargs] = BoolGetDatum(true); + slot->tts_isnull[nargs] = false; + ExecStoreVirtualTuple(slot); + + tuplesort_puttupleslot(sorter, slot); + + tuplesort_performsort(sorter); + + numDistinctCol = AggSetGetDistinctInfo(fcinfo, &slot2, &colidx, &equalfns); + + ExecClearTuple(slot2); + + AggSetGetPerTupleContext(fcinfo, &memcontext); + + while (tuplesort_gettupleslot(sorter, true, slot)) + { + TupleTableSlot *tmpslot = slot2; + bool isnull; + Datum d = slot_getattr(slot, nargs + 1, &isnull); + + if (!isnull && DatumGetBool(d)) + break; + + if (!TupIsNull(slot2) + && execTuplesMatch(slot, slot2, + (numDistinctCol - 1), + colidx, + equalfns, + memcontext)) + ++duplicate_count; + + slot2 = slot; + slot = tmpslot; + + ++rank; + } + + ExecClearTuple(slot); + ExecClearTuple(slot2); + + rank = rank - duplicate_count; + PG_RETURN_INT64(rank); + } + + /* percent_rank(float8) + * Calculates the relative ranking of hypothetical + * row within a group + */ + + Datum + hypothetical_percent_rank_final(PG_FUNCTION_ARGS) + { + Datum rank = hypothetical_rank_final(fcinfo); + int64 rank_val = DatumGetInt64(rank); + int64 rowcount = AggSetGetRowCount(fcinfo) + 1; + + float8 result_val = (float8) (rank_val - 1) / (float8) (rowcount - 1); + + PG_RETURN_FLOAT8(result_val); + } + + /* cume_dist - cumulative distribution of hypothetical + * row in a group + */ + + Datum + hypothetical_cume_dist_final(PG_FUNCTION_ARGS) + { + Datum rank = hypothetical_rank_final(fcinfo); + int64 rank_val = DatumGetInt64(rank); + int64 rowcount = AggSetGetRowCount(fcinfo) + 1; + + float8 result_val = (float8) (rank_val) / (float8) (rowcount); + + PG_RETURN_FLOAT8(result_val); + } *** /dev/null --- b/src/backend/utils/adt/inversedistribution.c *************** *** 0 **** --- 1,662 ---- + /*------------------------------------------------------------------------- + * + * inversedistribution.c + * Inverse distribution 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/inversedistribution.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + #include "fmgr.h" + #include + #include + + #include "utils/tuplesort.h" + #include "catalog/pg_type.h" + #include "utils/datetime.h" + #include "utils/lsyscache.h" + #include "utils/array.h" + + /* + * percentile_disc(float8) - discrete percentile + */ + + Datum percentile_disc_final(PG_FUNCTION_ARGS); + + Datum + percentile_disc_final(PG_FUNCTION_ARGS) + { + float8 percentile; + Tuplesortstate *sorter; + Oid datumtype; + Datum val; + bool isnull; + int64 skiprows; + int64 rowcount = AggSetGetRowCount(fcinfo); + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + percentile = PG_GETARG_FLOAT8(0); + + AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype); + + if (percentile < 0 || percentile > 1 || isnan(percentile)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("percentile value %g must be between 0 and 1", percentile))); + + if (rowcount < 1) + PG_RETURN_NULL(); + + tuplesort_performsort(sorter); + + /* + * We need the smallest K such that (K/N) >= percentile. K starts at 1. + * Therefore K >= N*percentile + * Therefore K = ceil(N*percentile) + * So we skip K-1 rows (if K>0) and return the next row fetched. + * + * We don't actually expect to see nulls in the input, our strict flag + * should have filtered them out, but we're required to not crash if + * there is one. + */ + + skiprows = (int64) ceil(percentile * rowcount); + Assert(skiprows <= rowcount); + + while (--skiprows > 0) + if (!tuplesort_getdatum(sorter, true, NULL, NULL)) + elog(ERROR,"missing row in percentile_disc"); + + if (!tuplesort_getdatum(sorter, true, &val, &isnull)) + elog(ERROR,"missing row in percentile_disc"); + + 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. + */ + + static Datum float8_lerp(Datum lo, Datum hi, float8 pct) + { + float8 loval = DatumGetFloat8(lo); + float8 hival = DatumGetFloat8(hi); + return Float8GetDatum(loval + (pct * (hival - loval))); + } + + static Datum interval_lerp(Datum lo, Datum hi, float8 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); + } + + typedef Datum (*LerpFunc)(Datum lo, Datum hi, float8 pct); + + static Datum + percentile_cont_final_common(FunctionCallInfo fcinfo, + Oid expect_type, + LerpFunc lerpfunc) + { + float8 percentile; + int64 rowcount = AggSetGetRowCount(fcinfo); + Tuplesortstate *sorter; + Oid datumtype; + Datum val; + Datum first_row; + Datum second_row; + float8 proportion; + bool isnull; + int64 skiprows; + int64 lower_row = 0; + int64 higher_row = 0; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + percentile = PG_GETARG_FLOAT8(0); + + AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype); + + Assert(datumtype == expect_type); + + if (percentile < 0 || percentile > 1 || isnan(percentile)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("percentile value %g must be between 0 and 1", percentile))); + + if (rowcount < 1) + PG_RETURN_NULL(); + + tuplesort_performsort(sorter); + + lower_row = floor(percentile * (rowcount - 1)); + higher_row = ceil(percentile * (rowcount - 1)); + + Assert(lower_row < rowcount); + + for (skiprows = lower_row; skiprows > 0; --skiprows) + if (!tuplesort_getdatum(sorter, true, NULL, NULL)) + elog(ERROR,"missing row in percentile_cont"); + + if (!tuplesort_getdatum(sorter, true, &first_row, &isnull)) + elog(ERROR,"missing row in percentile_cont"); + if (isnull) + PG_RETURN_NULL(); + + if (lower_row == higher_row) + { + val = first_row; + } + else + { + if (!tuplesort_getdatum(sorter, true, &second_row, &isnull)) + elog(ERROR,"missing row in percentile_cont"); + + if (isnull) + PG_RETURN_NULL(); + + proportion = (percentile * (rowcount-1)) - lower_row; + val = lerpfunc(first_row, second_row, proportion); + } + + if (isnull) + PG_RETURN_NULL(); + else + PG_RETURN_DATUM(val); + } + + + + /* + * percentile_cont(float8) - continuous percentile + */ + + Datum percentile_cont_float8_final(PG_FUNCTION_ARGS); + Datum percentile_cont_interval_final(PG_FUNCTION_ARGS); + + Datum + percentile_cont_float8_final(PG_FUNCTION_ARGS) + { + return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp); + } + + /* + * percentile_interval_cont(Interval) - continuous percentile for Interval + */ + + Datum + percentile_cont_interval_final(PG_FUNCTION_ARGS) + { + return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp); + } + + + /* + * mode() - most common value + */ + + Datum mode_final(PG_FUNCTION_ARGS); + + Datum + mode_final(PG_FUNCTION_ARGS) + { + Tuplesortstate *sorter; + Oid datumtype; + bool isnull; + Datum val; + Datum last_val; + bool last_val_is_mode = false; + int64 val_freq = 0; + Datum mode_val; + int64 mode_freq = 0; + FmgrInfo *equalfn; + bool shouldfree; + + struct mode_type_info { + Oid typid; + int16 typLen; + bool typByVal; + } *typinfo = fcinfo->flinfo->fn_extra; + + AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype); + AggSetGetDistinctInfo(fcinfo, NULL, NULL, &equalfn); + + if (!typinfo || typinfo->typid != datumtype) + { + if (typinfo) + pfree(typinfo); + typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(struct mode_type_info)); + typinfo->typid = datumtype; + get_typlenbyval(datumtype, &typinfo->typLen, &typinfo->typByVal); + } + + shouldfree = !(typinfo->typByVal); + + tuplesort_performsort(sorter); + + while (tuplesort_getdatum(sorter, true, &val, &isnull)) + { + if (isnull) + continue; + + if (val_freq == 0) + { + /* first value - assume modal until shown otherwise */ + mode_val = last_val = val; + mode_freq = val_freq = 1; + last_val_is_mode = true; + } + else if (DatumGetBool(FunctionCall2(equalfn, val, last_val))) + { + /* value equal to previous value */ + if (last_val_is_mode) + ++mode_freq; + else if (++val_freq > mode_freq) + { + if (shouldfree) + { + pfree(DatumGetPointer(mode_val)); + pfree(DatumGetPointer(val)); + } + + mode_val = last_val; + mode_freq = val_freq; + last_val_is_mode = true; + } + else if (shouldfree) + pfree(DatumGetPointer(val)); + } + else + { + if (shouldfree && !last_val_is_mode) + pfree(DatumGetPointer(last_val)); + + last_val_is_mode = false; + last_val = val; + val_freq = 1; + } + } + + if (shouldfree && !last_val_is_mode) + pfree(DatumGetPointer(last_val)); + + if (mode_freq) + PG_RETURN_DATUM(mode_val); + else + PG_RETURN_NULL(); + } + + + + /* + * percentile_disc(float8[]) - discrete percentiles + */ + + Datum percentile_disc_multi_final(PG_FUNCTION_ARGS); + + struct pct_info { + int64 first_row; + int64 second_row; + float8 proportion; + int idx; + }; + + static int pct_info_cmp(const void *pa, const void *pb) + { + const struct pct_info *a = pa; + const struct pct_info *b = pb; + if (a->first_row == b->first_row) + return (a->second_row < b->second_row) ? -1 : (a->second_row == b->second_row) ? 0 : 1; + else + return (a->first_row < b->first_row) ? -1 : 1; + } + + 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 = palloc(num_percentiles * sizeof(struct pct_info)); + int i; + + for (i = 0; i < num_percentiles; i++) + { + pct_info[i].idx = i; + + if (percentiles_null[i]) + { + pct_info[i].first_row = 0; + pct_info[i].second_row = 0; + pct_info[i].proportion = 0; + } + else + { + float8 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 must be 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. K starts at 1. + * Therefore K >= N*percentile + * Therefore K = ceil(N*percentile), minimum 1 + */ + + pct_info[i].first_row = Max(1, (int64) ceil(rowcount * p)); + pct_info[i].second_row = 0; + pct_info[i].proportion = 0; + } + } + } + + qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp); + + return pct_info; + } + + Datum + percentile_disc_multi_final(PG_FUNCTION_ARGS) + { + ArrayType *param; + Datum *percentiles_datum; + bool *percentiles_null; + int num_percentiles; + int64 rowcount = AggSetGetRowCount(fcinfo); + int64 rownum = 0; + Tuplesortstate *sorter; + Oid datumtype; + Datum val; + bool isnull; + Datum *result_datum; + bool *result_isnull; + int i; + struct pct_info *pct_info; + + struct mode_type_info { + Oid typid; + int16 typLen; + bool typByVal; + char typAlign; + } *typinfo = fcinfo->flinfo->fn_extra; + + if (PG_ARGISNULL(0) || rowcount < 1) + PG_RETURN_NULL(); + + AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype); + + if (!typinfo || typinfo->typid != datumtype) + { + if (typinfo) + pfree(typinfo); + typinfo = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(struct mode_type_info)); + typinfo->typid = datumtype; + get_typlenbyvalalign(datumtype, + &typinfo->typLen, + &typinfo->typByVal, + &typinfo->typAlign); + } + + param = PG_GETARG_ARRAYTYPE_P(0); + + deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd', + &percentiles_datum, &percentiles_null, &num_percentiles); + + if (num_percentiles == 0) + PG_RETURN_POINTER(construct_empty_array(datumtype)); + + result_datum = palloc0(num_percentiles * sizeof(Datum)); + result_isnull = palloc0(num_percentiles * sizeof(bool)); + + pct_info = setup_pct_info(num_percentiles, + percentiles_datum, + percentiles_null, + rowcount, + false); + + /* + * 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) + { + tuplesort_performsort(sorter); + + for (; i < num_percentiles; ++i) + { + int64 target_row = pct_info[i].first_row; + int idx = pct_info[i].idx; + + if (target_row > rownum) + { + while (target_row > ++rownum) + { + if (!tuplesort_getdatum(sorter, true, NULL, NULL)) + elog(ERROR,"missing row in percentile_disc"); + } + + if (!tuplesort_getdatum(sorter, true, &val, &isnull)) + elog(ERROR,"missing row in percentile_disc"); + } + + result_datum[idx] = val; + result_isnull[idx] = isnull; + } + } + + /* 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), + datumtype, + typinfo->typLen, + typinfo->typByVal, + typinfo->typAlign)); + } + + static Datum + percentile_cont_multi_final_common(FunctionCallInfo fcinfo, + Oid expect_type, + int16 typLen, bool typByVal, char typAlign, + LerpFunc lerpfunc) + { + ArrayType *param; + Datum *percentiles_datum; + bool *percentiles_null; + int num_percentiles; + int64 rowcount = AggSetGetRowCount(fcinfo); + int64 rownum = 0; + int64 rownum_second = 0; + Tuplesortstate *sorter; + Oid datumtype; + Datum first_val; + Datum second_val; + bool isnull; + Datum *result_datum; + bool *result_isnull; + int i; + struct pct_info *pct_info; + + if (PG_ARGISNULL(0) || rowcount < 1) + PG_RETURN_NULL(); + + AggSetGetSortInfo(fcinfo, &sorter, NULL, NULL, &datumtype); + Assert(datumtype == expect_type); + + param = PG_GETARG_ARRAYTYPE_P(0); + + deconstruct_array(param, FLOAT8OID, 8, FLOAT8PASSBYVAL, 'd', + &percentiles_datum, &percentiles_null, &num_percentiles); + + if (num_percentiles == 0) + PG_RETURN_POINTER(construct_empty_array(datumtype)); + + result_datum = palloc0(num_percentiles * sizeof(Datum)); + result_isnull = palloc0(num_percentiles * sizeof(bool)); + + pct_info = setup_pct_info(num_percentiles, + percentiles_datum, + percentiles_null, + rowcount, + true); + + /* + * 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) + { + tuplesort_performsort(sorter); + + 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; + + if (target_row > rownum_second) + { + rownum = rownum_second; + + while (target_row > ++rownum) + { + if (!tuplesort_getdatum(sorter, true, NULL, NULL)) + elog(ERROR,"missing row in percentile_cont"); + } + + if (!tuplesort_getdatum(sorter, true, &first_val, &isnull) || isnull) + elog(ERROR,"missing row in percentile_cont"); + + rownum_second = rownum; + + if (need_lerp) + { + if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull) + elog(ERROR,"missing row in percentile_cont"); + ++rownum_second; + } + } + else if (target_row == rownum_second) + { + first_val = second_val; + rownum = rownum_second; + + if (need_lerp) + { + if (!tuplesort_getdatum(sorter, true, &second_val, &isnull) || isnull) + elog(ERROR,"missing row in percentile_cont"); + ++rownum_second; + } + } + + 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 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); + Datum percentile_cont_interval_multi_final(PG_FUNCTION_ARGS); + + Datum + percentile_cont_float8_multi_final(PG_FUNCTION_ARGS) + { + return percentile_cont_multi_final_common(fcinfo, + FLOAT8OID, 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, 16, false, 'd', + interval_lerp); + } *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** *** 22,27 **** --- 22,28 ---- #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" *************** *** 293,298 **** static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname, --- 294,302 ---- static int print_function_arguments(StringInfo buf, HeapTuple proctup, bool print_table_args, bool print_defaults); static void print_function_rettype(StringInfo buf, HeapTuple proctup); + static void print_aggregate_arguments(StringInfo buf, + HeapTuple proctup, HeapTuple aggtup, + bool print_defaults); static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces, Bitmapset *rels_used); static bool refname_is_unique(char *refname, deparse_namespace *dpns, *************** *** 402,407 **** static char *generate_function_name(Oid funcid, int nargs, --- 406,413 ---- static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); + static void get_aggstd_expr(Aggref *aggref, deparse_context *context); + static void get_ordset_expr(Aggref *aggref, deparse_context *context); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") *************** *** 2267,2272 **** print_function_arguments(StringInfo buf, HeapTuple proctup, --- 2273,2421 ---- /* + * pg_get_aggregate_arguments + * Get a nicely-formatted list of arguments for an aggregate. + * This is everything that would go after the function name + * in CREATE AGGREGATE, _including_ the parens, because in the + * case of ordered set funcs, we emit the WITHIN GROUP clause + * too. + */ + Datum + pg_get_aggregate_arguments(PG_FUNCTION_ARGS) + { + Oid funcid = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple proctup; + HeapTuple aggtup; + + initStringInfo(&buf); + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(aggtup)) + elog(ERROR, "function %u is not an aggregate function", funcid); + + (void) print_aggregate_arguments(&buf, proctup, aggtup, true); + + ReleaseSysCache(aggtup); + ReleaseSysCache(proctup); + + PG_RETURN_TEXT_P(string_to_text(buf.data)); + } + + /* + * pg_get_aggregate_identity_arguments + * Get a formatted list of arguments for an aggregate. + * This is everything that would go after the function name in + * ALTER AGGREGATE, etc. In particular, don't print defaults. + * Currently, this is identical to pg_get_aggregate_arguments, + * but if we ever allow defaults that will change. + */ + Datum + pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS) + { + Oid funcid = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple proctup; + HeapTuple aggtup; + + initStringInfo(&buf); + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for function %u", funcid); + + aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(aggtup)) + elog(ERROR, "function %u is not an aggregate function", funcid); + + (void) print_aggregate_arguments(&buf, proctup, aggtup, false); + + ReleaseSysCache(aggtup); + ReleaseSysCache(proctup); + + PG_RETURN_TEXT_P(string_to_text(buf.data)); + } + + + /* + * Common code for pg_get_aggregate_arguments + * We print argument defaults only if print_defaults is true. + */ + static void + print_aggregate_arguments(StringInfo buf, + HeapTuple proctup, HeapTuple aggtup, + bool print_defaults) + { + Form_pg_aggregate agg = (Form_pg_aggregate) GETSTRUCT(aggtup); + int numargs; + bool ordsetfunc = agg->aggisordsetfunc; + int numdirectargs = agg->aggordnargs; + Oid *argtypes; + char **argnames; + char *argmodes; + int i; + + /* defaults not supported at this time */ + (void) print_defaults; + + numargs = get_func_arg_info(proctup, + &argtypes, &argnames, &argmodes); + + appendStringInfoChar(buf, '('); + + for (i = 0; i < numargs; i++) + { + Oid argtype = argtypes[i]; + char *argname = argnames ? argnames[i] : NULL; + char argmode = argmodes ? argmodes[i] : PROARGMODE_IN; + const char *modename; + + switch (argmode) + { + case PROARGMODE_IN: + modename = ""; + break; + case PROARGMODE_VARIADIC: + modename = "VARIADIC "; + break; + default: + elog(ERROR, "invalid parameter mode '%c'", argmode); + modename = NULL; /* keep compiler quiet */ + break; + } + + if (i == numdirectargs) + { + appendStringInfoString(buf, ") WITHIN GROUP ("); + } + else if (i > 0) + appendStringInfoString(buf, ", "); + + appendStringInfoString(buf, modename); + + if (argname && argname[0]) + appendStringInfo(buf, "%s ", quote_identifier(argname)); + + appendStringInfoString(buf, format_type_be(argtype)); + } + + if (ordsetfunc) + { + if (numdirectargs < 0 || numdirectargs == numargs) + appendStringInfoString(buf, ") WITHIN GROUP (*"); + } + else if (numargs == 0) + appendStringInfoChar(buf, '*'); + + appendStringInfoChar(buf, ')'); + } + + + /* * deparse_expression - General utility for deparsing expressions * * calls deparse_expression_pretty with all prettyPrinting disabled *************** *** 7388,7393 **** static void --- 7537,7616 ---- get_agg_expr(Aggref *aggref, deparse_context *context) { StringInfo buf = context->buf; + + if (aggref->isordset) + { + get_ordset_expr(aggref, context); + } + else + { + get_aggstd_expr(aggref, context); + } + + if (aggref->aggfilter != NULL) + { + appendStringInfoString(buf, ") FILTER (WHERE "); + get_rule_expr((Node *)aggref->aggfilter, context, false); + } + + appendStringInfoString(buf, ")"); + } + + static void + get_ordset_expr(Aggref *aggref, deparse_context *context) + { + StringInfo buf = context->buf; + Oid argtypes[FUNC_MAX_ARGS]; + List *arglist; + int nargs; + ListCell *l; + + arglist = NIL; + nargs = 0; + + foreach(l, aggref->orddirectargs) + { + Node *arg = (Node *) lfirst(l); + + Assert(!IsA(arg, NamedArgExpr)); + if (nargs >= FUNC_MAX_ARGS) /* paranoia */ + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg("too many arguments"))); + argtypes[nargs] = exprType(arg); + nargs++; + } + + /* For direct arguments in case of ordered set functions */ + foreach(l, aggref->args) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + Node *arg = (Node *) tle->expr; + + Assert(!IsA(arg, NamedArgExpr)); + 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++; + } + + appendStringInfo(buf, "%s(", + generate_function_name(aggref->aggfnoid, nargs, + NIL, argtypes, + false, NULL)); + + get_rule_expr((Node *)aggref->orddirectargs, context, true); + appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY "); + get_rule_orderby(aggref->aggorder, aggref->args, false, context); + + } + static void + get_aggstd_expr(Aggref *aggref, deparse_context *context) + { + StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; List *arglist; int nargs; *************** *** 7442,7455 **** get_agg_expr(Aggref *aggref, deparse_context *context) appendStringInfoString(buf, " ORDER BY "); get_rule_orderby(aggref->aggorder, aggref->args, false, context); } - - if (aggref->aggfilter != NULL) - { - appendStringInfoString(buf, ") FILTER (WHERE "); - get_rule_expr((Node *) aggref->aggfilter, context, false); - } - - appendStringInfoChar(buf, ')'); } /* --- 7665,7670 ---- *** a/src/backend/utils/sort/tuplesort.c --- b/src/backend/utils/sort/tuplesort.c *************** *** 1411,1433 **** tuplesort_performsort(Tuplesortstate *state) * Internal routine to fetch the next tuple in either forward or back * direction into *stup. Returns FALSE if no more tuples. * If *should_free is set, the caller must pfree stup.tuple when done with it. */ static bool tuplesort_gettuple_common(Tuplesortstate *state, bool forward, SortTuple *stup, bool *should_free) { unsigned int tuplen; switch (state->status) { case TSS_SORTEDINMEM: Assert(forward || state->randomAccess); ! *should_free = false; if (forward) { if (state->current < state->memtupcount) { ! *stup = state->memtuples[state->current++]; return true; } state->eof_reached = true; --- 1411,1439 ---- * Internal routine to fetch the next tuple in either forward or back * direction into *stup. Returns FALSE if no more tuples. * If *should_free is set, the caller must pfree stup.tuple when done with it. + * stup may be null to move without fetching. */ static bool tuplesort_gettuple_common(Tuplesortstate *state, bool forward, SortTuple *stup, bool *should_free) { unsigned int tuplen; + SortTuple dummy; + SortTuple *ptup = stup ? stup : &dummy; switch (state->status) { case TSS_SORTEDINMEM: Assert(forward || state->randomAccess); ! if (should_free) ! *should_free = false; if (forward) { if (state->current < state->memtupcount) { ! if (stup) ! *stup = state->memtuples[state->current]; ! state->current++; return true; } state->eof_reached = true; *************** *** 1459,1479 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward, if (state->current <= 0) return false; } ! *stup = state->memtuples[state->current - 1]; return true; } break; case TSS_SORTEDONTAPE: Assert(forward || state->randomAccess); ! *should_free = true; if (forward) { if (state->eof_reached) return false; if ((tuplen = getlen(state, state->result_tape, true)) != 0) { ! READTUP(state, stup, state->result_tape, tuplen); return true; } else --- 1465,1489 ---- if (state->current <= 0) return false; } ! if (stup) ! *stup = state->memtuples[state->current - 1]; return true; } break; case TSS_SORTEDONTAPE: Assert(forward || state->randomAccess); ! if (should_free) ! *should_free = true; if (forward) { if (state->eof_reached) return false; if ((tuplen = getlen(state, state->result_tape, true)) != 0) { ! READTUP(state, ptup, state->result_tape, tuplen); ! if (!stup && dummy.tuple) ! pfree(dummy.tuple); return true; } else *************** *** 1546,1557 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward, state->result_tape, tuplen)) elog(ERROR, "bogus tuple length in backward scan"); ! READTUP(state, stup, state->result_tape, tuplen); return true; case TSS_FINALMERGE: Assert(forward); ! *should_free = true; /* * This code should match the inner loop of mergeonerun(). --- 1556,1570 ---- state->result_tape, tuplen)) elog(ERROR, "bogus tuple length in backward scan"); ! READTUP(state, ptup, state->result_tape, tuplen); ! if (!stup && dummy.tuple) ! pfree(dummy.tuple); return true; case TSS_FINALMERGE: Assert(forward); ! if (should_free) ! *should_free = true; /* * This code should match the inner loop of mergeonerun(). *************** *** 1563,1573 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward, int tupIndex; SortTuple *newtup; ! *stup = state->memtuples[0]; /* returned tuple is no longer counted in our memory space */ ! if (stup->tuple) { ! tuplen = GetMemoryChunkSpace(stup->tuple); state->availMem += tuplen; state->mergeavailmem[srcTape] += tuplen; } --- 1576,1586 ---- int tupIndex; SortTuple *newtup; ! *ptup = state->memtuples[0]; /* returned tuple is no longer counted in our memory space */ ! if (ptup->tuple) { ! tuplen = GetMemoryChunkSpace(ptup->tuple); state->availMem += tuplen; state->mergeavailmem[srcTape] += tuplen; } *************** *** 1598,1603 **** tuplesort_gettuple_common(Tuplesortstate *state, bool forward, --- 1611,1618 ---- newtup->tupindex = state->mergefreelist; state->mergefreelist = tupIndex; state->mergeavailslots[srcTape]++; + if (!stup && dummy.tuple) + pfree(dummy.tuple); return true; } return false; *************** *** 1620,1639 **** tuplesort_gettupleslot(Tuplesortstate *state, bool forward, MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext); SortTuple stup; bool should_free; ! if (!tuplesort_gettuple_common(state, forward, &stup, &should_free)) ! stup.tuple = NULL; MemoryContextSwitchTo(oldcontext); ! if (stup.tuple) { ! ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free); return true; } else { ! ExecClearTuple(slot); return false; } } --- 1635,1656 ---- MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext); SortTuple stup; bool should_free; + bool found; ! found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free); MemoryContextSwitchTo(oldcontext); ! if (found) { ! if (slot) ! ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free); return true; } else { ! if (slot) ! ExecClearTuple(slot); return false; } } *************** *** 1692,1715 **** tuplesort_getdatum(Tuplesortstate *state, bool forward, SortTuple stup; bool should_free; ! if (!tuplesort_gettuple_common(state, forward, &stup, &should_free)) { MemoryContextSwitchTo(oldcontext); return false; } ! if (stup.isnull1 || state->datumTypeByVal) { ! *val = stup.datum1; ! *isNull = stup.isnull1; ! } ! else ! { ! if (should_free) *val = stup.datum1; else ! *val = datumCopy(stup.datum1, false, state->datumTypeLen); ! *isNull = false; } MemoryContextSwitchTo(oldcontext); --- 1709,1735 ---- SortTuple stup; bool should_free; ! if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free)) { MemoryContextSwitchTo(oldcontext); return false; } ! if (val) { ! if (stup.isnull1 || state->datumTypeByVal) ! { *val = stup.datum1; + *isNull = stup.isnull1; + } else ! { ! if (should_free) ! *val = stup.datum1; ! else ! *val = datumCopy(stup.datum1, false, state->datumTypeLen); ! *isNull = false; ! } } MemoryContextSwitchTo(oldcontext); *** a/src/bin/pg_dump/pg_dump.c --- b/src/bin/pg_dump/pg_dump.c *************** *** 229,234 **** static void getTableData(TableInfo *tblinfo, int numTables, bool oids); --- 229,235 ---- static void makeTableDataInfo(TableInfo *tbinfo, bool oids); static void buildMatViewRefreshDependencies(Archive *fout); static void getTableDataFKConstraints(void); + static char *format_aggregate_arguments(FuncInfo *finfo, char *funcargs); static char *format_function_arguments(FuncInfo *finfo, char *funcargs, bool is_agg); static char *format_function_arguments_old(Archive *fout, *************** *** 9363,9368 **** dumpProcLang(Archive *fout, ProcLangInfo *plang) --- 9364,9385 ---- } /* + * format_aggregate_arguments: generate function name and argument list + * + * This is used when we can rely on pg_get_aggregate_arguments to format + * the argument list. + */ + static char * + format_aggregate_arguments(FuncInfo *finfo, char *funcargs) + { + PQExpBufferData fn; + + initPQExpBuffer(&fn); + appendPQExpBuffer(&fn, "%s%s", fmtId(finfo->dobj.name), funcargs); + return fn.data; + } + + /* * format_function_arguments: generate function name and argument list * * This is used when we can rely on pg_get_function_arguments to format *************** *** 11418,11432 **** dumpAgg(Archive *fout, AggInfo *agginfo) --- 11435,11456 ---- int i_aggtransfn; int i_aggfinalfn; int i_aggsortop; + int i_aggtranssortop; + int i_hypothetical; + int i_isstrict; int i_aggtranstype; int i_agginitval; int i_convertok; const char *aggtransfn; const char *aggfinalfn; const char *aggsortop; + const char *aggtranssortop; const char *aggtranstype; const char *agginitval; + bool hypothetical; + bool isstrict; bool convertok; + bool has_comma = false; /* Skip if not to be dumped */ if (!agginfo->aggfn.dobj.dump || dataOnly) *************** *** 11442,11452 **** dumpAgg(Archive *fout, AggInfo *agginfo) selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name); /* Get aggregate-specific details */ ! if (fout->remoteVersion >= 80400) { appendPQExpBuffer(query, "SELECT aggtransfn, " "aggfinalfn, aggtranstype::pg_catalog.regtype, " "aggsortop::pg_catalog.regoperator, " "agginitval, " "'t'::boolean AS convertok, " "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, " --- 11466,11496 ---- selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name); /* Get aggregate-specific details */ ! if (fout->remoteVersion >= 90400) ! { ! appendPQExpBuffer(query, "SELECT aggtransfn, " ! "aggfinalfn, aggtranstype::pg_catalog.regtype, " ! "aggsortop::pg_catalog.regoperator, " ! "aggtranssortop::pg_catalog.regoperator, " ! "(aggordnargs = -2) as hypothetical, " ! "p.proisstrict as isstrict, " ! "agginitval, " ! "'t'::boolean AS convertok, " ! "pg_catalog.pg_get_aggregate_arguments(p.oid) AS funcargs, " ! "pg_catalog.pg_get_aggregate_identity_arguments(p.oid) AS funciargs " ! "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p " ! "WHERE a.aggfnoid = p.oid " ! "AND p.oid = '%u'::pg_catalog.oid", ! agginfo->aggfn.dobj.catId.oid); ! } ! else if (fout->remoteVersion >= 80400) { appendPQExpBuffer(query, "SELECT aggtransfn, " "aggfinalfn, aggtranstype::pg_catalog.regtype, " "aggsortop::pg_catalog.regoperator, " + "0 as aggtranssortop, " + "false as hypothetical, " + "false as isstrict, " "agginitval, " "'t'::boolean AS convertok, " "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, " *************** *** 11461,11466 **** dumpAgg(Archive *fout, AggInfo *agginfo) --- 11505,11513 ---- appendPQExpBuffer(query, "SELECT aggtransfn, " "aggfinalfn, aggtranstype::pg_catalog.regtype, " "aggsortop::pg_catalog.regoperator, " + "0 as aggtranssortop, " + "false as hypothetical, " + "false as isstrict, " "agginitval, " "'t'::boolean AS convertok " "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p " *************** *** 11473,11478 **** dumpAgg(Archive *fout, AggInfo *agginfo) --- 11520,11528 ---- appendPQExpBuffer(query, "SELECT aggtransfn, " "aggfinalfn, aggtranstype::pg_catalog.regtype, " "0 AS aggsortop, " + "0 as aggtranssortop, " + "'f'::boolean as hypothetical, " + "'f'::boolean as isstrict, " "agginitval, " "'t'::boolean AS convertok " "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p " *************** *** 11485,11490 **** dumpAgg(Archive *fout, AggInfo *agginfo) --- 11535,11543 ---- appendPQExpBuffer(query, "SELECT aggtransfn, aggfinalfn, " "format_type(aggtranstype, NULL) AS aggtranstype, " "0 AS aggsortop, " + "0 as aggtranssortop, " + "'f'::boolean as hypothetical, " + "'f'::boolean as isstrict, " "agginitval, " "'t'::boolean AS convertok " "FROM pg_aggregate " *************** *** 11497,11502 **** dumpAgg(Archive *fout, AggInfo *agginfo) --- 11550,11558 ---- "aggfinalfn, " "(SELECT typname FROM pg_type WHERE oid = aggtranstype1) AS aggtranstype, " "0 AS aggsortop, " + "0 as aggtranssortop, " + "'f'::boolean as hypothetical, " + "'f'::boolean as isstrict, " "agginitval1 AS agginitval, " "(aggtransfn2 = 0 and aggtranstype2 = 0 and agginitval2 is null) AS convertok " "FROM pg_aggregate " *************** *** 11509,11514 **** dumpAgg(Archive *fout, AggInfo *agginfo) --- 11565,11573 ---- i_aggtransfn = PQfnumber(res, "aggtransfn"); i_aggfinalfn = PQfnumber(res, "aggfinalfn"); i_aggsortop = PQfnumber(res, "aggsortop"); + i_aggtranssortop = PQfnumber(res, "aggtranssortop"); + i_hypothetical = PQfnumber(res, "hypothetical"); + i_isstrict = PQfnumber(res, "isstrict"); i_aggtranstype = PQfnumber(res, "aggtranstype"); i_agginitval = PQfnumber(res, "agginitval"); i_convertok = PQfnumber(res, "convertok"); *************** *** 11516,11526 **** dumpAgg(Archive *fout, AggInfo *agginfo) aggtransfn = PQgetvalue(res, 0, i_aggtransfn); aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn); aggsortop = PQgetvalue(res, 0, i_aggsortop); aggtranstype = PQgetvalue(res, 0, i_aggtranstype); agginitval = PQgetvalue(res, 0, i_agginitval); convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't'); ! if (fout->remoteVersion >= 80400) { /* 8.4 or later; we rely on server-side code for most of the work */ char *funcargs; --- 11575,11599 ---- aggtransfn = PQgetvalue(res, 0, i_aggtransfn); aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn); aggsortop = PQgetvalue(res, 0, i_aggsortop); + aggtranssortop = PQgetvalue(res, 0, i_aggtranssortop); + hypothetical = (PQgetvalue(res, 0, i_hypothetical)[0] == 't'); + isstrict = (PQgetvalue(res, 0, i_isstrict)[0] == 't'); aggtranstype = PQgetvalue(res, 0, i_aggtranstype); agginitval = PQgetvalue(res, 0, i_agginitval); convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't'); ! if (fout->remoteVersion >= 90400) ! { ! /* 9.4 or later; we rely on server-side code for almost all of the work */ ! char *funcargs; ! char *funciargs; ! ! funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs")); ! funciargs = PQgetvalue(res, 0, PQfnumber(res, "funciargs")); ! aggfullsig = format_aggregate_arguments(&agginfo->aggfn, funcargs); ! aggsig = format_aggregate_arguments(&agginfo->aggfn, funciargs); ! } ! else if (fout->remoteVersion >= 80400) { /* 8.4 or later; we rely on server-side code for most of the work */ char *funcargs; *************** *** 11550,11585 **** dumpAgg(Archive *fout, AggInfo *agginfo) if (fout->remoteVersion >= 70300) { /* If using 7.3's regproc or regtype, data is already quoted */ ! appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s", ! aggtransfn, ! aggtranstype); } else if (fout->remoteVersion >= 70100) { /* format_type quotes, regproc does not */ ! appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s", fmtId(aggtransfn), aggtranstype); } else { /* need quotes all around */ ! appendPQExpBuffer(details, " SFUNC = %s,\n", fmtId(aggtransfn)); appendPQExpBuffer(details, " STYPE = %s", fmtId(aggtranstype)); } ! if (!PQgetisnull(res, 0, i_agginitval)) { ! appendPQExpBuffer(details, ",\n INITCOND = "); ! appendStringLiteralAH(details, agginitval, fout); } ! if (strcmp(aggfinalfn, "-") != 0) { ! appendPQExpBuffer(details, ",\n FINALFUNC = %s", ! aggfinalfn); } aggsortop = convertOperatorReference(fout, aggsortop); --- 11623,11680 ---- if (fout->remoteVersion >= 70300) { /* If using 7.3's regproc or regtype, data is already quoted */ ! /* ! * either or both of SFUNC and STYPE might be missing in >90400, ! * but if SFUNC is missing, then FINALFUNC will always be present, ! * and if SFUNC is present then STYPE must also be present; the ! * code below relies on these conditions to keep the commas in the ! * right places. STRICT must be forced to false if SFUNC is present. ! */ ! ! if (strcmp(aggtransfn,"-") != 0) ! { ! appendPQExpBuffer(details, "\n SFUNC = %s,", aggtransfn); ! isstrict = false; ! } ! ! if (strcmp(aggtranstype,"-") != 0) ! appendPQExpBuffer(details, "\n STYPE = %s", aggtranstype); ! else ! has_comma = true; } else if (fout->remoteVersion >= 70100) { /* format_type quotes, regproc does not */ ! appendPQExpBuffer(details, "\n SFUNC = %s,\n STYPE = %s", fmtId(aggtransfn), aggtranstype); } else { /* need quotes all around */ ! appendPQExpBuffer(details, "\n SFUNC = %s,\n", fmtId(aggtransfn)); appendPQExpBuffer(details, " STYPE = %s", fmtId(aggtranstype)); } ! if (strcmp(aggfinalfn, "-") != 0) { ! appendPQExpBuffer(details, "%s\n FINALFUNC = %s", ! (has_comma ? "" : ","), ! aggfinalfn); } ! if (hypothetical) ! appendPQExpBuffer(details, ",\n HYPOTHETICAL"); ! ! if (isstrict) ! appendPQExpBuffer(details, ",\n STRICT"); ! ! if (!PQgetisnull(res, 0, i_agginitval)) { ! appendPQExpBuffer(details, ",\n INITCOND = "); ! appendStringLiteralAH(details, agginitval, fout); } aggsortop = convertOperatorReference(fout, aggsortop); *************** *** 11589,11594 **** dumpAgg(Archive *fout, AggInfo *agginfo) --- 11684,11696 ---- aggsortop); } + aggtranssortop = convertOperatorReference(fout, aggtranssortop); + if (aggtranssortop) + { + appendPQExpBuffer(details, ",\n TRANSSORTOP = %s", + aggtranssortop); + } + /* * DROP must be fully qualified in case same name appears in pg_catalog */ *************** *** 11596,11602 **** dumpAgg(Archive *fout, AggInfo *agginfo) fmtId(agginfo->aggfn.dobj.namespace->dobj.name), aggsig); ! appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n", aggfullsig, details->data); appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig); --- 11698,11704 ---- fmtId(agginfo->aggfn.dobj.namespace->dobj.name), aggsig); ! appendPQExpBuffer(q, "CREATE AGGREGATE %s (%s\n);\n", aggfullsig, details->data); appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig); *************** *** 11625,11631 **** dumpAgg(Archive *fout, AggInfo *agginfo) /* * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL * command look like a function's GRANT; in particular this affects the ! * syntax for zero-argument aggregates. */ free(aggsig); free(aggsig_tag); --- 11727,11733 ---- /* * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL * command look like a function's GRANT; in particular this affects the ! * syntax for zero-argument aggregates and ordered set functions. */ free(aggsig); free(aggsig_tag); *** a/src/bin/psql/describe.c --- b/src/bin/psql/describe.c *************** *** 72,78 **** describeAggregates(const char *pattern, bool verbose, bool showSystem) gettext_noop("Name"), gettext_noop("Result data type")); ! if (pset.sversion >= 80400) appendPQExpBuffer(&buf, " CASE WHEN p.pronargs = 0\n" " THEN CAST('*' AS pg_catalog.text)\n" --- 72,82 ---- gettext_noop("Name"), gettext_noop("Result data type")); ! if (pset.sversion >= 90400) ! appendPQExpBuffer(&buf, ! " pg_catalog.pg_get_aggregate_arguments(p.oid) AS \"%s\",\n", ! gettext_noop("Argument data types")); ! else if (pset.sversion >= 80400) appendPQExpBuffer(&buf, " CASE WHEN p.pronargs = 0\n" " THEN CAST('*' AS pg_catalog.text)\n" *************** *** 254,260 **** describeFunctions(const char *functypes, const char *pattern, bool verbose, bool gettext_noop("Schema"), gettext_noop("Name")); ! if (pset.sversion >= 80400) appendPQExpBuffer(&buf, " pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n" " pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n" --- 258,283 ---- gettext_noop("Schema"), gettext_noop("Name")); ! if (pset.sversion >= 90400) ! appendPQExpBuffer(&buf, ! " pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n" ! " CASE WHEN p.proisagg THEN pg_catalog.pg_get_aggregate_arguments(p.oid)\n" ! " ELSE pg_catalog.pg_get_function_arguments(p.oid) END as \"%s\",\n" ! " CASE\n" ! " WHEN p.proisagg THEN '%s'\n" ! " WHEN p.proiswindow THEN '%s'\n" ! " WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN '%s'\n" ! " ELSE '%s'\n" ! " END as \"%s\"", ! gettext_noop("Result data type"), ! gettext_noop("Argument data types"), ! /* translator: "agg" is short for "aggregate" */ ! gettext_noop("agg"), ! gettext_noop("window"), ! gettext_noop("trigger"), ! gettext_noop("normal"), ! gettext_noop("Type")); ! else if (pset.sversion >= 80400) appendPQExpBuffer(&buf, " pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n" " pg_catalog.pg_get_function_arguments(p.oid) as \"%s\",\n" *** a/src/include/catalog/pg_aggregate.h --- b/src/include/catalog/pg_aggregate.h *************** *** 32,37 **** --- 32,40 ---- * aggfinalfn final function (0 if none) * aggsortop associated sort operator (0 if none) * aggtranstype type of aggregate's transition (state) data + * aggtranssortop An optional sort operator for the type aggtranstype + * aggordnargs Number of direct arguments to aggregate. + * aggisordsetfunc A flag to represent whether a function is ordered set or not * agginitval initial value for transition state (can be NULL) * ---------------------------------------------------------------- */ *************** *** 44,49 **** CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS --- 47,55 ---- regproc aggfinalfn; Oid aggsortop; Oid aggtranstype; + Oid aggtranssortop; + int32 aggordnargs; + bool aggisordsetfunc; #ifdef CATALOG_VARLEN /* variable-length fields start here */ text agginitval; *************** *** 62,74 **** typedef FormData_pg_aggregate *Form_pg_aggregate; * ---------------- */ ! #define Natts_pg_aggregate 6 #define Anum_pg_aggregate_aggfnoid 1 #define Anum_pg_aggregate_aggtransfn 2 #define Anum_pg_aggregate_aggfinalfn 3 #define Anum_pg_aggregate_aggsortop 4 #define Anum_pg_aggregate_aggtranstype 5 ! #define Anum_pg_aggregate_agginitval 6 /* ---------------- --- 68,83 ---- * ---------------- */ ! #define Natts_pg_aggregate 9 #define Anum_pg_aggregate_aggfnoid 1 #define Anum_pg_aggregate_aggtransfn 2 #define Anum_pg_aggregate_aggfinalfn 3 #define Anum_pg_aggregate_aggsortop 4 #define Anum_pg_aggregate_aggtranstype 5 ! #define Anum_pg_aggregate_aggtranssortop 6 ! #define Anum_pg_aggregate_aggordnargs 7 ! #define Anum_pg_aggregate_aggisordsetfunc 8 ! #define Anum_pg_aggregate_agginitval 9 /* ---------------- *************** *** 77,239 **** typedef FormData_pg_aggregate *Form_pg_aggregate; */ /* avg */ ! DATA(insert ( 2100 int8_avg_accum numeric_avg 0 1231 "{0,0}" )); ! DATA(insert ( 2101 int4_avg_accum int8_avg 0 1016 "{0,0}" )); ! DATA(insert ( 2102 int2_avg_accum int8_avg 0 1016 "{0,0}" )); ! DATA(insert ( 2103 numeric_avg_accum numeric_avg 0 1231 "{0,0}" )); ! DATA(insert ( 2104 float4_accum float8_avg 0 1022 "{0,0,0}" )); ! DATA(insert ( 2105 float8_accum float8_avg 0 1022 "{0,0,0}" )); ! DATA(insert ( 2106 interval_accum interval_avg 0 1187 "{0 second,0 second}" )); /* sum */ ! DATA(insert ( 2107 int8_sum - 0 1700 _null_ )); ! DATA(insert ( 2108 int4_sum - 0 20 _null_ )); ! DATA(insert ( 2109 int2_sum - 0 20 _null_ )); ! DATA(insert ( 2110 float4pl - 0 700 _null_ )); ! DATA(insert ( 2111 float8pl - 0 701 _null_ )); ! DATA(insert ( 2112 cash_pl - 0 790 _null_ )); ! DATA(insert ( 2113 interval_pl - 0 1186 _null_ )); ! DATA(insert ( 2114 numeric_add - 0 1700 _null_ )); /* max */ ! DATA(insert ( 2115 int8larger - 413 20 _null_ )); ! DATA(insert ( 2116 int4larger - 521 23 _null_ )); ! DATA(insert ( 2117 int2larger - 520 21 _null_ )); ! DATA(insert ( 2118 oidlarger - 610 26 _null_ )); ! DATA(insert ( 2119 float4larger - 623 700 _null_ )); ! DATA(insert ( 2120 float8larger - 674 701 _null_ )); ! DATA(insert ( 2121 int4larger - 563 702 _null_ )); ! DATA(insert ( 2122 date_larger - 1097 1082 _null_ )); ! DATA(insert ( 2123 time_larger - 1112 1083 _null_ )); ! DATA(insert ( 2124 timetz_larger - 1554 1266 _null_ )); ! DATA(insert ( 2125 cashlarger - 903 790 _null_ )); ! DATA(insert ( 2126 timestamp_larger - 2064 1114 _null_ )); ! DATA(insert ( 2127 timestamptz_larger - 1324 1184 _null_ )); ! DATA(insert ( 2128 interval_larger - 1334 1186 _null_ )); ! DATA(insert ( 2129 text_larger - 666 25 _null_ )); ! DATA(insert ( 2130 numeric_larger - 1756 1700 _null_ )); ! DATA(insert ( 2050 array_larger - 1073 2277 _null_ )); ! DATA(insert ( 2244 bpchar_larger - 1060 1042 _null_ )); ! DATA(insert ( 2797 tidlarger - 2800 27 _null_ )); ! DATA(insert ( 3526 enum_larger - 3519 3500 _null_ )); /* min */ ! DATA(insert ( 2131 int8smaller - 412 20 _null_ )); ! DATA(insert ( 2132 int4smaller - 97 23 _null_ )); ! DATA(insert ( 2133 int2smaller - 95 21 _null_ )); ! DATA(insert ( 2134 oidsmaller - 609 26 _null_ )); ! DATA(insert ( 2135 float4smaller - 622 700 _null_ )); ! DATA(insert ( 2136 float8smaller - 672 701 _null_ )); ! DATA(insert ( 2137 int4smaller - 562 702 _null_ )); ! DATA(insert ( 2138 date_smaller - 1095 1082 _null_ )); ! DATA(insert ( 2139 time_smaller - 1110 1083 _null_ )); ! DATA(insert ( 2140 timetz_smaller - 1552 1266 _null_ )); ! DATA(insert ( 2141 cashsmaller - 902 790 _null_ )); ! DATA(insert ( 2142 timestamp_smaller - 2062 1114 _null_ )); ! DATA(insert ( 2143 timestamptz_smaller - 1322 1184 _null_ )); ! DATA(insert ( 2144 interval_smaller - 1332 1186 _null_ )); ! DATA(insert ( 2145 text_smaller - 664 25 _null_ )); ! DATA(insert ( 2146 numeric_smaller - 1754 1700 _null_ )); ! DATA(insert ( 2051 array_smaller - 1072 2277 _null_ )); ! DATA(insert ( 2245 bpchar_smaller - 1058 1042 _null_ )); ! DATA(insert ( 2798 tidsmaller - 2799 27 _null_ )); ! DATA(insert ( 3527 enum_smaller - 3518 3500 _null_ )); /* count */ ! DATA(insert ( 2147 int8inc_any - 0 20 "0" )); ! DATA(insert ( 2803 int8inc - 0 20 "0" )); /* var_pop */ ! DATA(insert ( 2718 int8_accum numeric_var_pop 0 1231 "{0,0,0}" )); ! DATA(insert ( 2719 int4_accum numeric_var_pop 0 1231 "{0,0,0}" )); ! DATA(insert ( 2720 int2_accum numeric_var_pop 0 1231 "{0,0,0}" )); ! DATA(insert ( 2721 float4_accum float8_var_pop 0 1022 "{0,0,0}" )); ! DATA(insert ( 2722 float8_accum float8_var_pop 0 1022 "{0,0,0}" )); ! DATA(insert ( 2723 numeric_accum numeric_var_pop 0 1231 "{0,0,0}" )); /* var_samp */ ! DATA(insert ( 2641 int8_accum numeric_var_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2642 int4_accum numeric_var_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2643 int2_accum numeric_var_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2644 float4_accum float8_var_samp 0 1022 "{0,0,0}" )); ! DATA(insert ( 2645 float8_accum float8_var_samp 0 1022 "{0,0,0}" )); ! DATA(insert ( 2646 numeric_accum numeric_var_samp 0 1231 "{0,0,0}" )); /* variance: historical Postgres syntax for var_samp */ ! DATA(insert ( 2148 int8_accum numeric_var_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2149 int4_accum numeric_var_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2150 int2_accum numeric_var_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2151 float4_accum float8_var_samp 0 1022 "{0,0,0}" )); ! DATA(insert ( 2152 float8_accum float8_var_samp 0 1022 "{0,0,0}" )); ! DATA(insert ( 2153 numeric_accum numeric_var_samp 0 1231 "{0,0,0}" )); /* stddev_pop */ ! DATA(insert ( 2724 int8_accum numeric_stddev_pop 0 1231 "{0,0,0}" )); ! DATA(insert ( 2725 int4_accum numeric_stddev_pop 0 1231 "{0,0,0}" )); ! DATA(insert ( 2726 int2_accum numeric_stddev_pop 0 1231 "{0,0,0}" )); ! DATA(insert ( 2727 float4_accum float8_stddev_pop 0 1022 "{0,0,0}" )); ! DATA(insert ( 2728 float8_accum float8_stddev_pop 0 1022 "{0,0,0}" )); ! DATA(insert ( 2729 numeric_accum numeric_stddev_pop 0 1231 "{0,0,0}" )); /* stddev_samp */ ! DATA(insert ( 2712 int8_accum numeric_stddev_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2713 int4_accum numeric_stddev_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2714 int2_accum numeric_stddev_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2715 float4_accum float8_stddev_samp 0 1022 "{0,0,0}" )); ! DATA(insert ( 2716 float8_accum float8_stddev_samp 0 1022 "{0,0,0}" )); ! DATA(insert ( 2717 numeric_accum numeric_stddev_samp 0 1231 "{0,0,0}" )); /* stddev: historical Postgres syntax for stddev_samp */ ! DATA(insert ( 2154 int8_accum numeric_stddev_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2155 int4_accum numeric_stddev_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2156 int2_accum numeric_stddev_samp 0 1231 "{0,0,0}" )); ! DATA(insert ( 2157 float4_accum float8_stddev_samp 0 1022 "{0,0,0}" )); ! DATA(insert ( 2158 float8_accum float8_stddev_samp 0 1022 "{0,0,0}" )); ! DATA(insert ( 2159 numeric_accum numeric_stddev_samp 0 1231 "{0,0,0}" )); /* SQL2003 binary regression aggregates */ ! DATA(insert ( 2818 int8inc_float8_float8 - 0 20 "0" )); ! DATA(insert ( 2819 float8_regr_accum float8_regr_sxx 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2820 float8_regr_accum float8_regr_syy 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2821 float8_regr_accum float8_regr_sxy 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2822 float8_regr_accum float8_regr_avgx 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2823 float8_regr_accum float8_regr_avgy 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2824 float8_regr_accum float8_regr_r2 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2825 float8_regr_accum float8_regr_slope 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2826 float8_regr_accum float8_regr_intercept 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2827 float8_regr_accum float8_covar_pop 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2828 float8_regr_accum float8_covar_samp 0 1022 "{0,0,0,0,0,0}" )); ! DATA(insert ( 2829 float8_regr_accum float8_corr 0 1022 "{0,0,0,0,0,0}" )); /* boolean-and and boolean-or */ ! DATA(insert ( 2517 booland_statefunc - 58 16 _null_ )); ! DATA(insert ( 2518 boolor_statefunc - 59 16 _null_ )); ! DATA(insert ( 2519 booland_statefunc - 58 16 _null_ )); /* bitwise integer */ ! DATA(insert ( 2236 int2and - 0 21 _null_ )); ! DATA(insert ( 2237 int2or - 0 21 _null_ )); ! DATA(insert ( 2238 int4and - 0 23 _null_ )); ! DATA(insert ( 2239 int4or - 0 23 _null_ )); ! DATA(insert ( 2240 int8and - 0 20 _null_ )); ! DATA(insert ( 2241 int8or - 0 20 _null_ )); ! DATA(insert ( 2242 bitand - 0 1560 _null_ )); ! DATA(insert ( 2243 bitor - 0 1560 _null_ )); /* xml */ ! DATA(insert ( 2901 xmlconcat2 - 0 142 _null_ )); /* array */ ! DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 _null_ )); /* text */ ! DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 _null_ )); /* bytea */ ! DATA(insert ( 3545 bytea_string_agg_transfn bytea_string_agg_finalfn 0 2281 _null_ )); /* json */ ! DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 _null_ )); /* * prototypes for functions in pg_aggregate.c --- 86,261 ---- */ /* avg */ ! DATA(insert ( 2100 int8_avg_accum numeric_avg 0 1231 0 -1 f "{0,0}" )); ! DATA(insert ( 2101 int4_avg_accum int8_avg 0 1016 0 -1 f "{0,0}" )); ! DATA(insert ( 2102 int2_avg_accum int8_avg 0 1016 0 -1 f "{0,0}" )); ! DATA(insert ( 2103 numeric_avg_accum numeric_avg 0 1231 0 -1 f "{0,0}" )); ! DATA(insert ( 2104 float4_accum float8_avg 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2105 float8_accum float8_avg 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2106 interval_accum interval_avg 0 1187 0 -1 f "{0 second,0 second}" )); /* sum */ ! DATA(insert ( 2107 int8_sum - 0 1700 0 -1 f _null_ )); ! DATA(insert ( 2108 int4_sum - 0 20 0 -1 f _null_ )); ! DATA(insert ( 2109 int2_sum - 0 20 0 -1 f _null_ )); ! DATA(insert ( 2110 float4pl - 0 700 0 -1 f _null_ )); ! DATA(insert ( 2111 float8pl - 0 701 0 -1 f _null_ )); ! DATA(insert ( 2112 cash_pl - 0 790 0 -1 f _null_ )); ! DATA(insert ( 2113 interval_pl - 0 1186 0 -1 f _null_ )); ! DATA(insert ( 2114 numeric_add - 0 1700 0 -1 f _null_ )); /* max */ ! DATA(insert ( 2115 int8larger - 413 20 0 -1 f _null_ )); ! DATA(insert ( 2116 int4larger - 521 23 0 -1 f _null_ )); ! DATA(insert ( 2117 int2larger - 520 21 0 -1 f _null_ )); ! DATA(insert ( 2118 oidlarger - 610 26 0 -1 f _null_ )); ! DATA(insert ( 2119 float4larger - 623 700 0 -1 f _null_ )); ! DATA(insert ( 2120 float8larger - 674 701 0 -1 f _null_ )); ! DATA(insert ( 2121 int4larger - 563 702 0 -1 f _null_ )); ! DATA(insert ( 2122 date_larger - 1097 1082 0 -1 f _null_ )); ! DATA(insert ( 2123 time_larger - 1112 1083 0 -1 f _null_ )); ! DATA(insert ( 2124 timetz_larger - 1554 1266 0 -1 f _null_ )); ! DATA(insert ( 2125 cashlarger - 903 790 0 -1 f _null_ )); ! DATA(insert ( 2126 timestamp_larger - 2064 1114 0 -1 f _null_ )); ! DATA(insert ( 2127 timestamptz_larger - 1324 1184 0 -1 f _null_ )); ! DATA(insert ( 2128 interval_larger - 1334 1186 0 -1 f _null_ )); ! DATA(insert ( 2129 text_larger - 666 25 0 -1 f _null_ )); ! DATA(insert ( 2130 numeric_larger - 1756 1700 0 -1 f _null_ )); ! DATA(insert ( 2050 array_larger - 1073 2277 0 -1 f _null_ )); ! DATA(insert ( 2244 bpchar_larger - 1060 1042 0 -1 f _null_ )); ! DATA(insert ( 2797 tidlarger - 2800 27 0 -1 f _null_ )); ! DATA(insert ( 3526 enum_larger - 3519 3500 0 -1 f _null_ )); /* min */ ! DATA(insert ( 2131 int8smaller - 412 20 0 -1 f _null_ )); ! DATA(insert ( 2132 int4smaller - 97 23 0 -1 f _null_ )); ! DATA(insert ( 2133 int2smaller - 95 21 0 -1 f _null_ )); ! DATA(insert ( 2134 oidsmaller - 609 26 0 -1 f _null_ )); ! DATA(insert ( 2135 float4smaller - 622 700 0 -1 f _null_ )); ! DATA(insert ( 2136 float8smaller - 672 701 0 -1 f _null_ )); ! DATA(insert ( 2137 int4smaller - 562 702 0 -1 f _null_ )); ! DATA(insert ( 2138 date_smaller - 1095 1082 0 -1 f _null_ )); ! DATA(insert ( 2139 time_smaller - 1110 1083 0 -1 f _null_ )); ! DATA(insert ( 2140 timetz_smaller - 1552 1266 0 -1 f _null_ )); ! DATA(insert ( 2141 cashsmaller - 902 790 0 -1 f _null_ )); ! DATA(insert ( 2142 timestamp_smaller - 2062 1114 0 -1 f _null_ )); ! DATA(insert ( 2143 timestamptz_smaller - 1322 1184 0 -1 f _null_ )); ! DATA(insert ( 2144 interval_smaller - 1332 1186 0 -1 f _null_ )); ! DATA(insert ( 2145 text_smaller - 664 25 0 -1 f _null_ )); ! DATA(insert ( 2146 numeric_smaller - 1754 1700 0 -1 f _null_ )); ! DATA(insert ( 2051 array_smaller - 1072 2277 0 -1 f _null_ )); ! DATA(insert ( 2245 bpchar_smaller - 1058 1042 0 -1 f _null_ )); ! DATA(insert ( 2798 tidsmaller - 2799 27 0 -1 f _null_ )); ! DATA(insert ( 3527 enum_smaller - 3518 3500 0 -1 f _null_ )); /* count */ ! DATA(insert ( 2147 int8inc_any - 0 20 0 -1 f "0" )); ! DATA(insert ( 2803 int8inc - 0 20 0 -1 f "0" )); /* var_pop */ ! DATA(insert ( 2718 int8_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2719 int4_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2720 int2_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2721 float4_accum float8_var_pop 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2722 float8_accum float8_var_pop 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2723 numeric_accum numeric_var_pop 0 1231 0 -1 f "{0,0,0}" )); /* var_samp */ ! DATA(insert ( 2641 int8_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2642 int4_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2643 int2_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2644 float4_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2645 float8_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2646 numeric_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" )); /* variance: historical Postgres syntax for var_samp */ ! DATA(insert ( 2148 int8_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2149 int4_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2150 int2_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2151 float4_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2152 float8_accum float8_var_samp 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2153 numeric_accum numeric_var_samp 0 1231 0 -1 f "{0,0,0}" )); /* stddev_pop */ ! DATA(insert ( 2724 int8_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2725 int4_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2726 int2_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2727 float4_accum float8_stddev_pop 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2728 float8_accum float8_stddev_pop 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2729 numeric_accum numeric_stddev_pop 0 1231 0 -1 f "{0,0,0}" )); /* stddev_samp */ ! DATA(insert ( 2712 int8_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2713 int4_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2714 int2_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2715 float4_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2716 float8_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2717 numeric_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" )); /* stddev: historical Postgres syntax for stddev_samp */ ! DATA(insert ( 2154 int8_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2155 int4_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2156 int2_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2157 float4_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2158 float8_accum float8_stddev_samp 0 1022 0 -1 f "{0,0,0}" )); ! DATA(insert ( 2159 numeric_accum numeric_stddev_samp 0 1231 0 -1 f "{0,0,0}" )); /* SQL2003 binary regression aggregates */ ! DATA(insert ( 2818 int8inc_float8_float8 - 0 20 0 -1 f "0" )); ! DATA(insert ( 2819 float8_regr_accum float8_regr_sxx 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2820 float8_regr_accum float8_regr_syy 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2821 float8_regr_accum float8_regr_sxy 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2822 float8_regr_accum float8_regr_avgx 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2823 float8_regr_accum float8_regr_avgy 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2824 float8_regr_accum float8_regr_r2 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2825 float8_regr_accum float8_regr_slope 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2826 float8_regr_accum float8_regr_intercept 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2827 float8_regr_accum float8_covar_pop 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2828 float8_regr_accum float8_covar_samp 0 1022 0 -1 f "{0,0,0,0,0,0}" )); ! DATA(insert ( 2829 float8_regr_accum float8_corr 0 1022 0 -1 f "{0,0,0,0,0,0}" )); /* boolean-and and boolean-or */ ! DATA(insert ( 2517 booland_statefunc - 58 16 0 -1 f _null_ )); ! DATA(insert ( 2518 boolor_statefunc - 59 16 0 -1 f _null_ )); ! DATA(insert ( 2519 booland_statefunc - 58 16 0 -1 f _null_ )); /* bitwise integer */ ! DATA(insert ( 2236 int2and - 0 21 0 -1 f _null_ )); ! DATA(insert ( 2237 int2or - 0 21 0 -1 f _null_ )); ! DATA(insert ( 2238 int4and - 0 23 0 -1 f _null_ )); ! DATA(insert ( 2239 int4or - 0 23 0 -1 f _null_ )); ! DATA(insert ( 2240 int8and - 0 20 0 -1 f _null_ )); ! DATA(insert ( 2241 int8or - 0 20 0 -1 f _null_ )); ! DATA(insert ( 2242 bitand - 0 1560 0 -1 f _null_ )); ! DATA(insert ( 2243 bitor - 0 1560 0 -1 f _null_ )); /* xml */ ! DATA(insert ( 2901 xmlconcat2 - 0 142 0 -1 f _null_ )); /* array */ ! DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 0 -1 f _null_ )); /* text */ ! DATA(insert ( 3538 string_agg_transfn string_agg_finalfn 0 2281 0 -1 f _null_ )); /* bytea */ ! DATA(insert ( 3545 bytea_string_agg_transfn bytea_string_agg_finalfn 0 2281 0 -1 f _null_ )); /* json */ ! DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 0 -1 f _null_ )); ! ! /* ordered set functions */ ! DATA(insert ( 3931 - percentile_disc_final 0 0 0 1 t _null_)); ! DATA(insert ( 3935 - percentile_cont_float8_final 0 0 0 1 t _null_)); ! DATA(insert ( 3939 - percentile_cont_interval_final 0 0 0 1 t _null_)); ! DATA(insert ( 3968 - rank_final 0 16 59 -2 t "f")); ! DATA(insert ( 3970 - dense_rank_final 0 16 59 -2 t "f")); ! DATA(insert ( 3972 - percent_rank_final 0 16 59 -2 t "f")); ! DATA(insert ( 3974 - cume_dist_final 0 16 58 -2 t "f")); ! DATA(insert ( 3976 - mode_final 0 0 0 0 t _null_)); ! DATA(insert ( 3978 - percentile_disc_multi_final 0 0 0 1 t _null_)); ! DATA(insert ( 3980 - percentile_cont_float8_multi_final 0 0 0 1 t _null_)); ! DATA(insert ( 3982 - percentile_cont_interval_multi_final 0 0 0 1 t _null_)); /* * prototypes for functions in pg_aggregate.c *************** *** 241,246 **** DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 _null_ )); --- 263,269 ---- extern Oid AggregateCreate(const char *aggName, Oid aggNamespace, int numArgs, + int numDirectArgs, oidvector *parameterTypes, Datum allParameterTypes, Datum parameterModes, *************** *** 249,255 **** extern Oid AggregateCreate(const char *aggName, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, Oid aggTransType, ! const char *agginitval); #endif /* PG_AGGREGATE_H */ --- 272,282 ---- List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, + List *aggtranssortopName, Oid aggTransType, ! const char *agginitval, ! bool isStrict, ! bool isOrderedSetFunc, ! bool isHypotheticalSet); #endif /* PG_AGGREGATE_H */ *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 1964,1969 **** DATA(insert OID = 2232 ( pg_get_function_identity_arguments PGNSP PGUID 12 1 --- 1964,1973 ---- DESCR("identity argument list of a function"); DATA(insert OID = 2165 ( pg_get_function_result PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_function_result _null_ _null_ _null_ )); DESCR("result type of a function"); + DATA(insert OID = 3178 ( pg_get_aggregate_arguments PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_arguments _null_ _null_ _null_ )); + DESCR("argument list of an aggregate function"); + DATA(insert OID = 3179 ( pg_get_aggregate_identity_arguments PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_aggregate_identity_arguments _null_ _null_ _null_ )); + DESCR("identity argument list of an aggregate function"); DATA(insert OID = 1686 ( pg_get_keywords PGNSP PGUID 12 10 400 0 0 f f f f t t s 0 0 2249 "" "{25,18,25}" "{o,o,o}" "{word,catcode,catdesc}" _null_ pg_get_keywords _null_ _null_ _null_ )); DESCR("list of SQL keywords"); *************** *** 4729,4734 **** DESCR("SP-GiST support for quad tree over range"); --- 4733,4806 ---- /* event triggers */ DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ )); DESCR("list objects dropped by the current command"); + + /* inverse distribution functions */ + DATA(insert OID = 3931 ( percentile_disc PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("discrete percentile"); + + DATA(insert OID = 3932 ( percentile_disc_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2283 "701 2283" _null_ _null_ _null_ _null_ percentile_disc_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + DATA(insert OID = 3935 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("continous distribution percentile for float8"); + + DATA(insert OID = 3936 ( percentile_cont_float8_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 701 "701 701" _null_ _null_ _null_ _null_ percentile_cont_float8_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + DATA(insert OID = 3939 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("continous distribution percentile for interval"); + + DATA(insert OID = 3940 ( percentile_cont_interval_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1186 "701 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + /* hypothetical set functions */ + DATA(insert OID = 3968 ( rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("hypothetical rank"); + + DATA(insert OID = 3969 ( rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ hypothetical_rank_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + DATA(insert OID = 3970 ( dense_rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("rank of hypothetical row without gaps"); + + DATA(insert OID = 3971 ( dense_rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 20 2276 "{2276}" "{v}" _null_ _null_ hypothetical_dense_rank_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + DATA(insert OID = 3972 ( percent_rank PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("fractional ranking of hypothetical row within a group"); + + DATA(insert OID = 3973 ( percent_rank_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ hypothetical_percent_rank_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + DATA(insert OID = 3974 ( cume_dist PGNSP PGUID 12 1 0 2276 0 t f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("cumulative distribution of hypothetical row in a group"); + + DATA(insert OID = 3975 ( cume_dist_final PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 701 2276 "{2276}" "{v}" _null_ _null_ hypothetical_cume_dist_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + DATA(insert OID = 3976 ( mode PGNSP PGUID 12 1 0 0 0 t f f f t f i 1 0 2283 2283 _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("most common value in group"); + DATA(insert OID = 3977 ( mode_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2283 2283 _null_ _null_ _null_ _null_ mode_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + DATA(insert OID = 3978 ( percentile_disc PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("multiple discrete percentiles"); + + DATA(insert OID = 3979 ( percentile_disc_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "1022 2283" _null_ _null_ _null_ _null_ percentile_disc_multi_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + DATA(insert OID = 3980 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("multiple continuous percentiles of float8 values"); + + DATA(insert OID = 3981 ( percentile_cont_float8_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1022 "1022 701" _null_ _null_ _null_ _null_ percentile_cont_float8_multi_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + + DATA(insert OID = 3982 ( percentile_cont PGNSP PGUID 12 1 0 0 0 t f f f t f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ )); + DESCR("multiple continuous percentiles of interval values"); + + DATA(insert OID = 3983 ( percentile_cont_interval_multi_final PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 1187 "1022 1186" _null_ _null_ _null_ _null_ percentile_cont_interval_multi_final _null_ _null_ _null_ )); + DESCR("ordered set final function"); + /* * Symbolic values for provolatile column: these indicate whether the result * of a function is dependent *only* on the values of its explicit arguments, *** a/src/include/fmgr.h --- b/src/include/fmgr.h *************** *** 651,656 **** extern void **find_rendezvous_variable(const char *varName); --- 651,685 ---- extern int AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext); + typedef struct Tuplesortstate fmTuplesortstate; + typedef struct tupleDesc *fmTupleDesc; + typedef struct TupleTableSlot fmTupleTableSlot; + + extern int64 AggSetGetRowCount(FunctionCallInfo fcinfo); + + extern void AggSetGetSortInfo(FunctionCallInfo fcinfo, + fmTuplesortstate **sortstate, + fmTupleDesc *tupdesc, + fmTupleTableSlot **tupslot, + Oid *datumtype); + + /* int16 rather than AttrNumber here to avoid includes */ + extern int AggSetGetDistinctInfo(FunctionCallInfo fcinfo, + fmTupleTableSlot **tupslot, + int16 **sortColIdx, + FmgrInfo **equalfns); + + /* int16 rather than AttrNumber here to avoid includes */ + extern int AggSetGetSortOperators(FunctionCallInfo fcinfo, + int16 **sortColIdx, + Oid **sortOperators, + Oid **sortEqOperators, + Oid **sortCollations, + bool **sortNullsFirst); + + extern void AggSetGetPerTupleContext(FunctionCallInfo fcinfo, + MemoryContext *memcontext); + /* * We allow plugin modules to hook function entry/exit. This is intended * as support for loadable security policy modules, which may want to *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 588,593 **** typedef struct AggrefExprState --- 588,594 ---- { ExprState xprstate; List *args; /* states of argument expressions */ + List *orddirectargs; /* Ordered direct arguments */ ExprState *aggfilter; /* FILTER expression */ int aggno; /* ID number for agg within its plan node */ } AggrefExprState; *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** *** 425,431 **** typedef enum NodeTag T_WindowObjectData, /* private in nodeWindowAgg.c */ T_TIDBitmap, /* in nodes/tidbitmap.h */ T_InlineCodeBlock, /* in nodes/parsenodes.h */ ! T_FdwRoutine /* in foreign/fdwapi.h */ } NodeTag; /* --- 425,432 ---- T_WindowObjectData, /* private in nodeWindowAgg.c */ T_TIDBitmap, /* in nodes/tidbitmap.h */ T_InlineCodeBlock, /* in nodes/parsenodes.h */ ! T_FdwRoutine, /* in foreign/fdwapi.h */ ! T_AggStatePerAggData /* private in nodeAgg.c */ } NodeTag; /* *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 303,308 **** typedef struct FuncCall --- 303,309 ---- bool agg_star; /* argument was really '*' */ bool agg_distinct; /* arguments were labeled DISTINCT */ bool func_variadic; /* last argument was labeled VARIADIC */ + bool has_within_group; /* WITHIN GROUP clause,if any */ struct WindowDef *over; /* OVER clause, if any */ int location; /* token location, or -1 if unknown */ } FuncCall; *** a/src/include/nodes/primnodes.h --- b/src/include/nodes/primnodes.h *************** *** 247,255 **** typedef struct Aggref --- 247,258 ---- List *args; /* arguments and sort expressions */ List *aggorder; /* ORDER BY (list of SortGroupClause) */ List *aggdistinct; /* DISTINCT (list of SortGroupClause) */ + List *orddirectargs; /* Direct arguments for ordered set functions */ Expr *aggfilter; /* FILTER expression */ bool aggstar; /* TRUE if argument list was really '*' */ bool aggvariadic; /* TRUE if VARIADIC was used in call */ + bool isordset; /* If node is from an ordered set function */ + bool ishypothetical; /* If node is from a hypothetical set function */ Index agglevelsup; /* > 0 if agg belongs to outer query */ int location; /* token location, or -1 if unknown */ } Aggref; *** a/src/include/parser/kwlist.h --- b/src/include/parser/kwlist.h *************** *** 412,417 **** PG_KEYWORD("where", WHERE, RESERVED_KEYWORD) --- 412,418 ---- PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD) PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD) PG_KEYWORD("with", WITH, RESERVED_KEYWORD) + PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD) PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD) PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD) PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD) *** a/src/include/parser/parse_agg.h --- b/src/include/parser/parse_agg.h *************** *** 17,23 **** extern void transformAggregateCall(ParseState *pstate, Aggref *agg, List *args, List *aggorder, ! bool agg_distinct); extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, WindowDef *windef); --- 17,23 ---- extern void transformAggregateCall(ParseState *pstate, Aggref *agg, List *args, List *aggorder, ! bool agg_distinct, bool agg_within_group); extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, WindowDef *windef); *************** *** 34,37 **** extern void build_aggregate_fnexprs(Oid *agg_input_types, --- 34,51 ---- Expr **transfnexpr, Expr **finalfnexpr); + void + build_orderedset_fnexprs(Oid *agg_input_types, + int agg_num_inputs, + bool agg_variadic, + Oid agg_result_type, + Oid agg_input_collation, + Oid *agg_input_collation_array, + Oid finalfn_oid, + Expr **finalfnexpr); + + int get_aggregate_argtypes(Aggref *aggref, + Oid *inputTypes, + Oid *inputCollations); + #endif /* PARSE_AGG_H */ *** a/src/include/parser/parse_clause.h --- b/src/include/parser/parse_clause.h *************** *** 31,37 **** extern List *transformGroupClause(ParseState *pstate, List *grouplist, ParseExprKind exprKind, bool useSQL99); extern List *transformSortClause(ParseState *pstate, List *orderlist, List **targetlist, ParseExprKind exprKind, ! bool resolveUnknown, bool useSQL99); extern List *transformWindowDefinitions(ParseState *pstate, List *windowdefs, --- 31,37 ---- ParseExprKind exprKind, bool useSQL99); extern List *transformSortClause(ParseState *pstate, List *orderlist, List **targetlist, ParseExprKind exprKind, ! bool resolveUnknown, bool useSQL99, bool keepDuplicates); extern List *transformWindowDefinitions(ParseState *pstate, List *windowdefs, *** a/src/include/parser/parse_func.h --- b/src/include/parser/parse_func.h *************** *** 38,51 **** typedef enum FUNCDETAIL_NORMAL, /* found a matching regular function */ FUNCDETAIL_AGGREGATE, /* found a matching aggregate function */ FUNCDETAIL_WINDOWFUNC, /* found a matching window function */ ! FUNCDETAIL_COERCION /* it's a type coercion request */ } FuncDetailCode; - extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ! List *agg_order, Expr *agg_filter, ! bool agg_star, bool agg_distinct, bool func_variadic, ! WindowDef *over, bool is_column, int location); extern FuncDetailCode func_get_detail(List *funcname, List *fargs, List *fargnames, --- 38,48 ---- FUNCDETAIL_NORMAL, /* found a matching regular function */ FUNCDETAIL_AGGREGATE, /* found a matching aggregate function */ FUNCDETAIL_WINDOWFUNC, /* found a matching window function */ ! FUNCDETAIL_COERCION, /* it's a type coercion request */ } FuncDetailCode; extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ! int location, FuncCall *fn); extern FuncDetailCode func_get_detail(List *funcname, List *fargs, List *fargnames, *************** *** 66,73 **** extern FuncCandidateList func_select_candidate(int nargs, extern void make_fn_arguments(ParseState *pstate, List *fargs, Oid *actual_arg_types, ! Oid *declared_arg_types); extern const char *funcname_signature_string(const char *funcname, int nargs, List *argnames, const Oid *argtypes); --- 63,72 ---- extern void make_fn_arguments(ParseState *pstate, List *fargs, + List *agg_order, Oid *actual_arg_types, ! Oid *declared_arg_types, ! bool requiresUnification); extern const char *funcname_signature_string(const char *funcname, int nargs, List *argnames, const Oid *argtypes); *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 658,663 **** extern Datum pg_get_functiondef(PG_FUNCTION_ARGS); --- 658,665 ---- extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS); extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS); extern Datum pg_get_function_result(PG_FUNCTION_ARGS); + extern Datum pg_get_aggregate_arguments(PG_FUNCTION_ARGS); + extern Datum pg_get_aggregate_identity_arguments(PG_FUNCTION_ARGS); extern char *deparse_expression(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit); extern List *deparse_context_for(const char *aliasname, Oid relid); *** a/src/test/regress/expected/aggregates.out --- b/src/test/regress/expected/aggregates.out *************** *** 1249,1254 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1) --- 1249,1461 ---- {"(2,2,bar)","(3,1,baz)"} (1 row) + -- ordered set functions + select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) + group by p order by p; + p | percentile_cont + ------+----------------- + 0 | 1 + 0.1 | 1.4 + 0.25 | 2 + 0.4 | 2.6 + 0.5 | 3 + 0.6 | 3.4 + 0.75 | 4 + 0.9 | 4.6 + 1 | 5 + (9 rows) + + select p, percentile_cont(p order by p) within group (order by x::float8) + from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) + group by p order by x; + ERROR: Cannot have multiple ORDER BY clauses with WITHIN GROUP + LINE 1: select p, percentile_cont(p order by p) within group (order ... + ^ + select p, sum() within group (order by x::float8) + from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p; + ERROR: sum(double precision) is not an ordered set function + select p, percentile_cont(p,p) from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p; + ERROR: WITHIN GROUP is required for call to ordered set function percentile_cont + LINE 1: select p, percentile_cont(p,p) from generate_series(1,5) x, + ^ + select percentile_cont(0.5) within group (order by b) from aggtest; + percentile_cont + ------------------ + 53.4485001564026 + (1 row) + + select percentile_cont(0.5) within group (order by b),sum(b) from aggtest; + percentile_cont | sum + ------------------+--------- + 53.4485001564026 | 431.773 + (1 row) + + select percentile_cont(0.5) within group (order by thousand) from tenk1; + percentile_cont + ----------------- + 499.5 + (1 row) + + select percentile_disc(0.5) within group (order by thousand) from tenk1; + percentile_disc + ----------------- + 499 + (1 row) + + select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x); + rank + ------ + 5 + (1 row) + + select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x); + cume_dist + ----------- + 0.875 + (1 row) + + select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x); + percent_rank + -------------- + 0.5 + (1 row) + + select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x); + dense_rank + ------------ + 3 + (1 row) + + select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1; + percentile_disc + ---------------------------- + {0,99,249,499,749,899,999} + (1 row) + + select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1; + percentile_cont + ----------------------------- + {0,249.75,499.5,749.25,999} + (1 row) + + select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1; + percentile_disc + --------------------------------- + {{NULL,999,499},{749,249,NULL}} + (1 row) + + select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x) + from generate_series(1,6) x; + percentile_cont + ----------------------- + {1,6,2.25,4.75,3.5,5} + (1 row) + + select ten, mode() within group (order by string4) from tenk1 group by ten; + ten | mode + -----+-------- + 0 | HHHHxx + 1 | OOOOxx + 2 | VVVVxx + 3 | OOOOxx + 4 | HHHHxx + 5 | HHHHxx + 6 | OOOOxx + 7 | AAAAxx + 8 | VVVVxx + 9 | VVVVxx + (10 rows) + + select percentile_disc(array[0.25,0.5,0.75]) within group (order by x) + from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x); + percentile_disc + ----------------- + {fred,jill,jim} + (1 row) + + -- ordered set funcs can't use ungrouped direct args: + select rank(x) within group (order by x) from generate_series(1,5) x; + ERROR: column "x.x" must appear in the GROUP BY clause or be used in an aggregate function + LINE 1: select rank(x) within group (order by x) from generate_serie... + ^ + -- collation: + select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX")) + from (values ('fred'),('jim')) v(x); + pg_collation_for + ------------------ + "POSIX" + (1 row) + + -- hypothetical type unification and argument failures: + select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x); + ERROR: WITHIN GROUP types text and integer cannot be matched + LINE 1: select rank(3) within group (order by x) from (values ('fred... + ^ + select rank(3) within group (order by stringu1,stringu2) from tenk1; + ERROR: Incorrect number of arguments for hypothetical set function + LINE 1: select rank(3) within group (order by stringu1,stringu2) fro... + ^ + select rank('fred') within group (order by x) from generate_series(1,5) x; + ERROR: invalid input syntax for integer: "fred" + LINE 1: select rank('fred') within group (order by x) from generate_... + ^ + select rank('adam'::text collate "C") within group (order by x collate "POSIX") + from (values ('fred'),('jim')) v(x); + ERROR: collation mismatch between explicit collations "C" and "POSIX" + LINE 1: ...adam'::text collate "C") within group (order by x collate "P... + ^ + -- hypothetical type unification successes: + select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x); + rank + ------ + 1 + (1 row) + + select rank('3') within group (order by x) from generate_series(1,5) x; + rank + ------ + 3 + (1 row) + + -- deparse and multiple features: + create view aggordview1 as + select ten, + percentile_disc(0.5) within group (order by thousand) as p50, + percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px, + rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred) + from tenk1 + group by ten order by ten; + select pg_get_viewdef('aggordview1'); + pg_get_viewdef + -------------------------------------------------------------------------------------------------------------------------------- + SELECT tenk1.ten, + + percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) AS p50, + + percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY tenk1.thousand) FILTER (WHERE (tenk1.hundred = 1)) AS px, + + rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY tenk1.hundred, tenk1.string4 DESC, tenk1.hundred) AS rank + + FROM tenk1 + + GROUP BY tenk1.ten + + ORDER BY tenk1.ten; + (1 row) + + select * from aggordview1 order by ten; + ten | p50 | px | rank + -----+-----+-----+------ + 0 | 490 | | 101 + 1 | 491 | 401 | 101 + 2 | 492 | | 101 + 3 | 493 | | 101 + 4 | 494 | | 101 + 5 | 495 | | 67 + 6 | 496 | | 1 + 7 | 497 | | 1 + 8 | 498 | | 1 + 9 | 499 | | 1 + (10 rows) + + drop view aggordview1; -- variadic aggregates select least_agg(q1,q2) from int8_tbl; least_agg *** a/src/test/regress/expected/opr_sanity.out --- b/src/test/regress/expected/opr_sanity.out *************** *** 700,708 **** SELECT * FROM funcdescs -- **************** pg_aggregate **************** -- Look for illegal values in pg_aggregate fields. SELECT ctid, aggfnoid::oid FROM pg_aggregate as p1 ! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0; ctid | aggfnoid ------+---------- (0 rows) --- 700,716 ---- -- **************** pg_aggregate **************** -- Look for illegal values in pg_aggregate fields. + -- ordered set functions can't have transfns, and must + -- have finalfns, but may or may not have transtypes. + -- other aggs must have transfns and transtypes with + -- optional finalfns. SELECT ctid, aggfnoid::oid FROM pg_aggregate as p1 ! WHERE aggfnoid = 0 ! OR CASE WHEN aggisordsetfunc ! THEN aggtransfn <> 0 OR aggfinalfn = 0 ! ELSE aggtransfn = 0 OR aggtranstype = 0 ! END; ctid | aggfnoid ------+---------- (0 rows) *************** *** 764,771 **** WHERE a.aggfnoid = p.oid AND a.aggfinalfn = pfn.oid AND (pfn.proretset OR NOT binary_coercible(pfn.prorettype, p.prorettype) ! OR pfn.pronargs != 1 ! OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0])); aggfnoid | proname | oid | proname ----------+---------+-----+--------- (0 rows) --- 772,780 ---- a.aggfinalfn = pfn.oid AND (pfn.proretset OR NOT binary_coercible(pfn.prorettype, p.prorettype) ! OR (aggisordsetfunc IS FALSE ! AND (pfn.pronargs != 1 ! OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0])))); aggfnoid | proname | oid | proname ----------+---------+-----+--------- (0 rows) *************** *** 857,866 **** ORDER BY 1; count("any") | count() (1 row) ! -- For the same reason, we avoid creating built-in variadic aggregates. ! SELECT oid, proname ! FROM pg_proc AS p ! WHERE proisagg AND provariadic != 0; oid | proname -----+--------- (0 rows) --- 866,877 ---- count("any") | count() (1 row) ! -- For the same reason, we avoid creating built-in variadic aggregates, except ! -- ordered set functions (which have their own syntax and are not subject to ! -- the misplaced ORDER BY issue). ! SELECT p.oid, proname ! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid) ! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc; oid | proname -----+--------- (0 rows) *** a/src/test/regress/sql/aggregates.sql --- b/src/test/regress/sql/aggregates.sql *************** *** 481,486 **** select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1) --- 481,549 ---- from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), generate_series(1,2) i; + -- ordered set functions + + select p, percentile_cont(p) within group (order by x::float8) from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) + group by p order by p; + select p, percentile_cont(p order by p) within group (order by x::float8) + from generate_series(1,5) x, (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) + group by p order by x; + select p, sum() within group (order by x::float8) + from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p; + select p, percentile_cont(p,p) from generate_series(1,5) x, + (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) group by p order by p; + select percentile_cont(0.5) within group (order by b) from aggtest; + select percentile_cont(0.5) within group (order by b),sum(b) from aggtest; + select percentile_cont(0.5) within group (order by thousand) from tenk1; + select percentile_disc(0.5) within group (order by thousand) from tenk1; + select rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x); + select cume_dist(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x); + select percent_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x); + select dense_rank(3) within group (order by x) from (values (1),(1),(2),(2),(3),(3),(4)) v(x); + + select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) from tenk1; + select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) from tenk1; + select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) from tenk1; + select percentile_cont(array[0,1,0.25,0.75,0.5,1]) within group (order by x) + from generate_series(1,6) x; + + select ten, mode() within group (order by string4) from tenk1 group by ten; + + select percentile_disc(array[0.25,0.5,0.75]) within group (order by x) + from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x); + + -- ordered set funcs can't use ungrouped direct args: + select rank(x) within group (order by x) from generate_series(1,5) x; + + -- collation: + select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX")) + from (values ('fred'),('jim')) v(x); + + -- hypothetical type unification and argument failures: + select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x); + select rank(3) within group (order by stringu1,stringu2) from tenk1; + select rank('fred') within group (order by x) from generate_series(1,5) x; + select rank('adam'::text collate "C") within group (order by x collate "POSIX") + from (values ('fred'),('jim')) v(x); + -- hypothetical type unification successes: + select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x); + select rank('3') within group (order by x) from generate_series(1,5) x; + + -- deparse and multiple features: + create view aggordview1 as + select ten, + percentile_disc(0.5) within group (order by thousand) as p50, + percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px, + rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred) + from tenk1 + group by ten order by ten; + + select pg_get_viewdef('aggordview1'); + select * from aggordview1 order by ten; + drop view aggordview1; + -- variadic aggregates select least_agg(q1,q2) from int8_tbl; select least_agg(variadic array[q1,q2]) from int8_tbl; *** a/src/test/regress/sql/opr_sanity.sql --- b/src/test/regress/sql/opr_sanity.sql *************** *** 564,573 **** SELECT * FROM funcdescs -- **************** pg_aggregate **************** -- Look for illegal values in pg_aggregate fields. SELECT ctid, aggfnoid::oid FROM pg_aggregate as p1 ! WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0; -- Make sure the matching pg_proc entry is sensible, too. --- 564,581 ---- -- **************** pg_aggregate **************** -- Look for illegal values in pg_aggregate fields. + -- ordered set functions can't have transfns, and must + -- have finalfns, but may or may not have transtypes. + -- other aggs must have transfns and transtypes with + -- optional finalfns. SELECT ctid, aggfnoid::oid FROM pg_aggregate as p1 ! WHERE aggfnoid = 0 ! OR CASE WHEN aggisordsetfunc ! THEN aggtransfn <> 0 OR aggfinalfn = 0 ! ELSE aggtransfn = 0 OR aggtranstype = 0 ! END; -- Make sure the matching pg_proc entry is sensible, too. *************** *** 618,625 **** WHERE a.aggfnoid = p.oid AND a.aggfinalfn = pfn.oid AND (pfn.proretset OR NOT binary_coercible(pfn.prorettype, p.prorettype) ! OR pfn.pronargs != 1 ! OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0])); -- If transfn is strict then either initval should be non-NULL, or -- input type should match transtype so that the first non-null input --- 626,634 ---- a.aggfinalfn = pfn.oid AND (pfn.proretset OR NOT binary_coercible(pfn.prorettype, p.prorettype) ! OR (aggisordsetfunc IS FALSE ! AND (pfn.pronargs != 1 ! OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0])))); -- If transfn is strict then either initval should be non-NULL, or -- input type should match transtype so that the first non-null input *************** *** 685,695 **** WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND array_dims(p1.proargtypes) != array_dims(p2.proargtypes) ORDER BY 1; ! -- For the same reason, we avoid creating built-in variadic aggregates. ! SELECT oid, proname ! FROM pg_proc AS p ! WHERE proisagg AND provariadic != 0; -- For the same reason, built-in aggregates with default arguments are no good. --- 694,706 ---- array_dims(p1.proargtypes) != array_dims(p2.proargtypes) ORDER BY 1; ! -- For the same reason, we avoid creating built-in variadic aggregates, except ! -- ordered set functions (which have their own syntax and are not subject to ! -- the misplaced ORDER BY issue). ! SELECT p.oid, proname ! FROM pg_proc AS p JOIN pg_aggregate AS a ON (a.aggfnoid=p.oid) ! WHERE proisagg AND provariadic != 0 AND NOT a.aggisordsetfunc; -- For the same reason, built-in aggregates with default arguments are no good.