diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 9388df5..0342cbe 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -352,7 +352,7 @@ aggtransfn regproc pg_proc.oid - Transition function + Transition function (zero if none) aggfinalfn @@ -370,7 +370,25 @@ aggtranstype oid pg_type.oid - Data type of the aggregate function's internal transition (state) data + 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 diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 89f08af..5d76efe 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -12252,6 +12252,249 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab; + + 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_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_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 results) + + + + + +
+ + + 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 diff --git a/doc/src/sgml/ref/alter_aggregate.sgml b/doc/src/sgml/ref/alter_aggregate.sgml index aab5b2b..4fb3b6f 100644 --- a/doc/src/sgml/ref/alter_aggregate.sgml +++ b/doc/src/sgml/ref/alter_aggregate.sgml @@ -21,12 +21,17 @@ PostgreSQL documentation -ALTER AGGREGATE name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) +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,10 +153,11 @@ ALTER AGGREGATE myavg(integer) OWNER TO joe; - To move the aggregate function myavg for type - integer into schema myschema: + To move the ordered set function mypercentile with + direct argument of type float8 taking groups + of integer type into schema myschema: -ALTER AGGREGATE myavg(integer) SET SCHEMA myschema; +ALTER AGGREGATE mypercentile(float8) WITHIN GROUP (integer) SET SCHEMA myschema; diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml index a14fcb4..5d326ae 100644 --- a/doc/src/sgml/ref/alter_extension.sgml +++ b/doc/src/sgml/ref/alter_extension.sgml @@ -30,7 +30,7 @@ ALTER EXTENSION name DROP where member_object is: - AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) | + 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 | diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index e550500..c62fa44 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -23,7 +23,7 @@ PostgreSQL documentation COMMENT ON { - AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) | + 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 | diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml index 2b35fa4..45b67c3 100644 --- a/doc/src/sgml/ref/create_aggregate.sgml +++ b/doc/src/sgml/ref/create_aggregate.sgml @@ -29,6 +29,15 @@ 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,7 +79,7 @@ CREATE AGGREGATE name ( - An aggregate function is made from one or two ordinary + An ordinary aggregate function is made from one or two ordinary functions: a state transition function sfunc, @@ -165,6 +174,14 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; + 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,6 +295,11 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; 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,6 +327,39 @@ SELECT col FROM tab ORDER BY col USING sortop LIMIT 1; + + + 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. + + + diff --git a/doc/src/sgml/ref/drop_aggregate.sgml b/doc/src/sgml/ref/drop_aggregate.sgml index 06060fb..2c5ab01 100644 --- a/doc/src/sgml/ref/drop_aggregate.sgml +++ b/doc/src/sgml/ref/drop_aggregate.sgml @@ -21,9 +21,13 @@ PostgreSQL documentation -DROP AGGREGATE [ IF EXISTS ] - name ( [ argmode ] [ arg_name ] arg_data_type [ , ... ] ) +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 [ , ... ] ) diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml index 76c131f..ebca07b 100644 --- a/doc/src/sgml/ref/security_label.sgml +++ b/doc/src/sgml/ref/security_label.sgml @@ -25,7 +25,7 @@ SECURITY LABEL [ FOR provider ] ON { TABLE object_name | COLUMN table_name.column_name | - AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) | + AGGREGATE agg_name ( [ argmode ] [ argname ] agg_type [, ...] ) [ WITHIN GROUP ( * | [ argmode ] [ argname ] agg_type [, ...] ) ] | DATABASE object_name | DOMAIN object_name | EVENT TRIGGER object_name | diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 4f50f43..42f1a22 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -1706,6 +1706,54 @@ SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect + + 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 diff --git a/doc/src/sgml/xaggr.sgml b/doc/src/sgml/xaggr.sgml index 9ed7d99..fc51602 100644 --- a/doc/src/sgml/xaggr.sgml +++ b/doc/src/sgml/xaggr.sgml @@ -9,20 +9,17 @@ - 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. + 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. diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index d9e961e..044e667 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -46,6 +46,7 @@ Oid AggregateCreate(const char *aggName, Oid aggNamespace, int numArgs, + int numDirectArgs, oidvector *parameterTypes, Datum allParameterTypes, Datum parameterModes, @@ -54,24 +55,29 @@ AggregateCreate(const char *aggName, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, + List *aggtranssortopName, Oid aggTransType, - const char *agginitval) + 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; - Oid finalfn = InvalidOid; /* can be omitted */ - Oid sortop = InvalidOid; /* can be omitted */ + 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; - int nargs_transfn; + Oid *fnArgs = palloc((numArgs + 1) * sizeof(Oid)); Oid procOid; TupleDesc tupDesc; int i; @@ -83,8 +89,20 @@ AggregateCreate(const char *aggName, if (!aggName) elog(ERROR, "no aggregate name supplied"); - if (!aggtransfnName) - elog(ERROR, "aggregate must have a transition function"); + 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,6 +115,136 @@ AggregateCreate(const char *aggName, 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,53 +255,86 @@ 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); + if (!isOrderedSet) + { + /* find the transfn */ - /* - * 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)))); + fnArgs[0] = aggTransType; + memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid)); - tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn)); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "cache lookup failed for function %u", transfn); - proc = (Form_pg_proc) GETSTRUCT(tup); + transfn = lookup_agg_function(aggtransfnName, numArgs + 1, fnArgs, + &rettype); - /* - * 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)) + /* + * 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_INVALID_FUNCTION_DEFINITION), - errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type"))); + (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); } - ReleaseSysCache(tup); /* handle finalfn, if supplied */ - if (aggfinalfnName) + 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,6 +347,7 @@ AggregateCreate(const char *aggName, */ finaltype = aggTransType; } + Assert(OidIsValid(finaltype)); /* @@ -207,6 +389,18 @@ AggregateCreate(const char *aggName, 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,15 +411,17 @@ 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); + 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,7 +442,7 @@ AggregateCreate(const char *aggName, false, /* security invoker (currently not * definable for agg) */ false, /* isLeakProof */ - false, /* isStrict (not needed for agg) */ + isStrict, /* isStrict (needed for ordered set funcs) */ PROVOLATILE_IMMUTABLE, /* volatility (not * needed for agg) */ parameterTypes, /* paramTypes */ @@ -272,7 +468,11 @@ AggregateCreate(const char *aggName, 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,18 +490,23 @@ 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. + * 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 */ - referenced.classId = ProcedureRelationId; - referenced.objectId = transfn; - referenced.objectSubId = 0; - recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + 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,6 +526,24 @@ AggregateCreate(const char *aggName, 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; } diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c index 78af092..9774667 100644 --- a/src/backend/commands/aggregatecmds.c +++ b/src/backend/commands/aggregatecmds.c @@ -44,9 +44,12 @@ * 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. + * 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,18 +61,23 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, 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; - Oid transTypeId; 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,6 +88,14 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, 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,6 +122,12 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, 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,17 +135,35 @@ 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"))); + 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,8 +213,15 @@ 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, + /* + * 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,7 +238,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, } /* - * look up the aggregate's transtype. + * 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,18 +248,20 @@ 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 (transType) { - 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)))); + 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,13 +273,23 @@ 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) + if (transType) { - Oid typinput, - typioparam; + if (initval && transTypeType != TYPTYPE_PSEUDO) + { + Oid typinput, + typioparam; - getTypeInputInfo(transTypeId, &typinput, &typioparam); - (void) OidInputFunctionCall(typinput, initval, typioparam, -1); + 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,6 +298,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, return AggregateCreate(aggName, /* aggregate name */ aggNamespace, /* namespace */ numArgs, + numDirectArgs, parameterTypes, PointerGetDatum(allParameterTypes), PointerGetDatum(parameterModes), @@ -247,6 +307,10 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, transfuncName, /* step function name */ finalfuncName, /* final function name */ sortoperatorName, /* sort operator name */ + transsortoperatorName, /* transsort operator name */ transTypeId, /* transition data type */ - initval); /* initial condition */ + initval, /* initial condition */ + isStrict, /* is explicitly STRICT */ + isOrderedSet, /* If the function is an ordered set */ + ishypothetical); /* If the function is a hypothetical set */ } diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index ca754b4..2399446 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -274,8 +274,13 @@ 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) + /* + * 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"))); diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 90c2753..e6fb8b0 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -4410,6 +4410,7 @@ ExecInitExpr(Expr *node, PlanState *parent) astate->args = (List *) ExecInitExpr((Expr *) aggref->args, parent); + astate->orddirectargs = (List *) ExecInitExpr((Expr *) aggref->orddirectargs, parent); astate->aggfilter = ExecInitExpr(aggref->aggfilter, parent); diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index ff6a123..f216cd4 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -380,8 +380,8 @@ 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); + cref->location, + NULL); } return param; diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index e02a6ff..3f2cf8d 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -3,7 +3,7 @@ * nodeAgg.c * Routines to handle aggregate nodes. * - * ExecAgg evaluates each aggregate in the following steps: + * ExecAgg evaluates each normal aggregate in the following steps: * * transvalue = initcond * foreach input_tuple do @@ -66,6 +66,26 @@ * 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,10 +107,12 @@ #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,6 +127,8 @@ */ typedef struct AggStatePerAggData { + NodeTag type; + /* * These values are set up during ExecInitAgg() and do not change * thereafter: @@ -114,10 +138,25 @@ typedef struct AggStatePerAggData AggrefExprState *aggrefstate; Aggref *aggref; - /* number of input arguments for aggregate function proper */ + /* 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 */ + /* + * 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,12 +165,23 @@ 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. + * 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,6 +198,9 @@ typedef struct AggStatePerAggData 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,6 +257,9 @@ typedef struct AggStatePerAggData */ Tuplesortstate *sortstate; /* sort object, if DISTINCT or ORDER BY */ + + int64 number_of_rows; /* number of rows */ + } AggStatePerAggData; /* @@ -300,6 +356,8 @@ initialize_aggregates(AggState *aggstate, 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,14 +441,17 @@ advance_transition_function(AggState *aggstate, MemoryContext oldContext; Datum newVal; int i; + int numStrict = peraggstate->numStrict; - if (peraggstate->transfn.fn_strict) + 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 <= numArguments; i++) + for (i = 1; i <= numStrict; i++) { if (fcinfo->argnull[i]) return; @@ -506,24 +567,24 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup) if (peraggstate->numSortCols > 0) { + int numStrict = peraggstate->numStrict; + /* DISTINCT and/or ORDER BY case */ Assert(slot->tts_nvalid == peraggstate->numInputs); /* - * If the transfn is strict, we want to check for nullity before + * 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. Note that we must only check numArguments columns, - * not numInputs, since nullity in columns used only for sorting - * is not relevant here. + * of nulls. */ - if (peraggstate->transfn.fn_strict) + if (numStrict > 0) { - for (i = 0; i < nargs; i++) + for (i = 0; i < numStrict; i++) { if (slot->tts_isnull[i]) break; } - if (i < nargs) + if (i < numStrict) continue; } @@ -534,6 +595,8 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup) slot->tts_isnull[0]); else tuplesort_puttupleslot(peraggstate->sortstate, slot); + + peraggstate->number_of_rows++; } else { @@ -756,15 +819,66 @@ finalize_aggregate(AggState *aggstate, 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; - 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) + ++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 */ + /* + * 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,6 +1278,17 @@ 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.) + * 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,14 +1299,17 @@ 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); + 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,15 +1317,6 @@ 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. */ @@ -1568,10 +1687,12 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) 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,6 +1701,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) 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,31 +1726,18 @@ 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->type = T_AggStatePerAggData; + peraggstate->aggstate = aggstate; peraggstate->aggref = aggref; - numInputs = list_length(aggref->args); - peraggstate->numInputs = numInputs; - peraggstate->sortstate = NULL; + peraggstate->aggrefstate = aggrefstate; - /* - * 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); + peraggstate->isOrderedSet = isOrderedSet; - if (!tle->resjunk) - inputTypes[numArguments++] = exprType((Node *) tle->expr); - } - peraggstate->numArguments = numArguments; + /* 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,6 +1745,13 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) 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,25 +1763,37 @@ 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 */ + /* + * 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); - aggOwner = ((Form_pg_proc) GETSTRUCT(procTuple))->proowner; + procp = (Form_pg_proc) GETSTRUCT(procTuple); + aggOwner = procp->proowner; + is_strict = procp->proisstrict; ReleaseSysCache(procTuple); - aclresult = pg_proc_aclcheck(transfn_oid, aggOwner, - ACL_EXECUTE); - if (aclresult != ACLCHECK_OK) + 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); + InvokeFunctionExecuteHook(transfn_oid); + } + if (OidIsValid(finalfn_oid)) { aclresult = pg_proc_aclcheck(finalfn_oid, aggOwner, @@ -1674,17 +1805,37 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) } } + /* + * 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 (IsPolymorphicType(aggtranstype)) + if (OidIsValid(aggtranstype) && IsPolymorphicType(aggtranstype)) { /* have to fetch the agg's declared input types... */ Oid *declaredArgTypes; - int agg_nargs; + int agg_nargs; (void) get_func_signature(aggref->aggfnoid, - &declaredArgTypes, &agg_nargs); - Assert(agg_nargs == numArguments); + &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,35 +1844,82 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) pfree(declaredArgTypes); } + aggtranstypecoll = get_typcollation(aggtranstype); + /* 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 (!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); - get_typlenbyval(aggtranstype, - &peraggstate->transtypeLen, - &peraggstate->transtypeByVal); + 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,7 +1942,9 @@ 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 (OidIsValid(peraggstate->transfn_oid) + && peraggstate->transfn.fn_strict + && peraggstate->initValueIsNull) { if (numArguments < 1 || !IsBinaryCoercible(inputTypes[0], aggtranstype)) @@ -1754,21 +1954,8 @@ 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); + argexprs = aggref->args; + argexprstate = aggrefstate->args; /* * If we're doing either DISTINCT or ORDER BY, then we have a list of @@ -1777,6 +1964,11 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) * * 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,11 +1980,86 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) { sortlist = aggref->aggorder; numSortCols = list_length(sortlist); - numDistinctCols = 0; + 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,11 +2072,12 @@ 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], + get_typlenbyval(peraggstate->evaldesc->attrs[0]->atttypid, &peraggstate->inputtypeLen, &peraggstate->inputtypeByVal); } - else if (numDistinctCols > 0) + + if (numDistinctCols > 0 && (numInputs > 1 || isOrderedSet)) { /* we will need an extra slot to store prior values */ peraggstate->uniqslot = ExecInitExtraTupleSlot(estate); @@ -1822,50 +2090,47 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) (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, - aggref->args); + 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; - 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); + /* + * 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]); - fmgr_info(get_opcode(sortcl->eqop), &peraggstate->equalfns[i]); i++; } - Assert(i == numDistinctCols); + Assert(i == numSortCols); } ReleaseSysCache(aggTuple); @@ -2023,6 +2288,9 @@ ExecReScanAgg(AggState *node) * 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,3 +2331,118 @@ aggregate_dummy(PG_FUNCTION_ARGS) 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; + } +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1733da6..b9446d4 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1139,9 +1139,12 @@ _copyAggref(const Aggref *from) 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,6 +2177,7 @@ _copyFuncCall(const FuncCall *from) 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); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 7b29812..05c239c 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -196,9 +196,12 @@ _equalAggref(const Aggref *a, const Aggref *b) 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); @@ -2015,6 +2018,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b) 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); diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 4a7e793..4529dca 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -558,6 +558,7 @@ makeFuncCall(List *name, List *args, int location) n->agg_star = FALSE; n->agg_distinct = FALSE; n->func_variadic = FALSE; + n->has_within_group = FALSE; n->over = NULL; return n; } diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 908f397..e84b371 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -1631,6 +1631,11 @@ expression_tree_walker(Node *node, 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,7 +2160,9 @@ expression_tree_mutator(Node *node, 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; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 817b149..8f6e293 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -960,9 +960,12 @@ _outAggref(StringInfo str, const Aggref *node) 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,6 +2093,7 @@ _outFuncCall(StringInfo str, const FuncCall *node) 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); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index d325bb3..950645d 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -495,9 +495,12 @@ _readAggref(void) 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); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index add29f5..6c24582 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -39,6 +39,7 @@ #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,7 +465,6 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context) QualCost argcosts; Oid *inputTypes; int numArguments; - ListCell *l; Assert(aggref->agglevelsup == 0); @@ -486,7 +486,8 @@ 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(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,72 +505,91 @@ 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) + /* + * 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) { - TargetEntry *tle = (TargetEntry *) lfirst(l); + Assert(!aggref->isordset); - if (!tle->resjunk) - inputTypes[numArguments++] = exprType((Node *) tle->expr); - } + /* extract argument types (ignoring any ORDER BY expressions) */ + inputTypes = (Oid *) palloc(sizeof(Oid) * FUNC_MAX_ARGS); - /* 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); - } + numArguments = get_aggregate_argtypes(aggref, inputTypes, NULL); - /* - * 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; + /* 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 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 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 (numArguments > 0 && aggtranstype == inputTypes[0]) - aggtranstypmod = exprTypmod((Node *) linitial(aggref->args)); - else - aggtranstypmod = -1; + 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); + avgwidth = get_typavgwidth(aggtranstype, aggtranstypmod); + avgwidth = MAXALIGN(avgwidth); - costs->transitionSpace += avgwidth + 2 * sizeof(void *); + 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 if (aggtranstype == INTERNALOID) + else { - /* - * 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; + costs->transitionSpace = work_mem; /* just in case */ } /* @@ -3897,7 +3917,7 @@ 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); + make_fn_arguments(NULL, args, NULL, actual_arg_types, declared_arg_types, false); } /* diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index a9d1fec..ae168ad 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -951,7 +951,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) &qry->targetList, EXPR_KIND_ORDER_BY, true /* fix unknowns */ , - false /* allow SQL92 rules */ ); + false /* allow SQL92 rules */, + false /* don't add duplicates */); qry->groupClause = transformGroupClause(pstate, stmt->groupClause, @@ -1211,7 +1212,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) &qry->targetList, EXPR_KIND_ORDER_BY, true /* fix unknowns */ , - false /* allow SQL92 rules */ ); + false /* allow SQL92 rules */, + false /* don't add duplicates */ ); qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, EXPR_KIND_OFFSET, "OFFSET"); @@ -1435,7 +1437,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) &qry->targetList, EXPR_KIND_ORDER_BY, false /* no unknowns expected */ , - false /* allow SQL92 rules */ ); + false /* allow SQL92 rules */ , + false /* don't add duplicates */ ); /* restore namespace, remove jrte from rtable */ pstate->p_namespace = sv_namespace; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 11f6291..7cb3591 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -495,6 +495,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %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. @@ -597,7 +598,7 @@ 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 + WHEN WHERE WITHIN WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE XMLPI XMLROOT XMLSERIALIZE @@ -3704,7 +3705,7 @@ AlterExtensionContentsStmt: n->action = $4; n->objtype = OBJECT_AGGREGATE; n->objname = $6; - n->objargs = extractArgTypes($7); + n->objargs = extractArgTypes(linitial($7)); $$ = (Node *)n; } | ALTER EXTENSION name add_drop CAST '(' Typename AS Typename ')' @@ -5283,7 +5284,7 @@ CommentStmt: CommentStmt *n = makeNode(CommentStmt); n->objtype = OBJECT_AGGREGATE; n->objname = $4; - n->objargs = extractArgTypes($5); + n->objargs = extractArgTypes(linitial($5)); n->comment = $7; $$ = (Node *) n; } @@ -5449,7 +5450,7 @@ SecLabelStmt: n->provider = $3; n->objtype = OBJECT_AGGREGATE; n->objname = $6; - n->objargs = extractArgTypes($7); + n->objargs = extractArgTypes(linitial($7)); n->label = $9; $$ = (Node *) n; } @@ -6449,9 +6450,53 @@ aggr_arg: func_arg } ; -/* Zero-argument aggregates are named with * for consistency with COUNT(*) */ -aggr_args: '(' aggr_args_list ')' { $$ = $2; } - | '(' '*' ')' { $$ = NIL; } +/* + * 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: @@ -6657,7 +6702,7 @@ RemoveAggrStmt: DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_AGGREGATE; n->objects = list_make1($3); - n->arguments = list_make1(extractArgTypes($4)); + n->arguments = list_make1(extractArgTypes(linitial($4))); n->behavior = $5; n->missing_ok = false; n->concurrent = false; @@ -6668,7 +6713,7 @@ RemoveAggrStmt: DropStmt *n = makeNode(DropStmt); n->removeType = OBJECT_AGGREGATE; n->objects = list_make1($5); - n->arguments = list_make1(extractArgTypes($6)); + n->arguments = list_make1(extractArgTypes(linitial($6))); n->behavior = $7; n->missing_ok = true; n->concurrent = false; @@ -6884,7 +6929,7 @@ 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->objarg = extractArgTypes(linitial($4)); n->newname = $7; n->missing_ok = false; $$ = (Node *)n; @@ -7358,7 +7403,7 @@ AlterObjectSchemaStmt: AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); n->objectType = OBJECT_AGGREGATE; n->object = $3; - n->objarg = extractArgTypes($4); + n->objarg = extractArgTypes(linitial($4)); n->newschema = $7; n->missing_ok = false; $$ = (Node *)n; @@ -7587,7 +7632,7 @@ 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->objarg = extractArgTypes(linitial($4)); n->newowner = $7; $$ = (Node *)n; } @@ -9475,6 +9520,11 @@ sortby: a_expr USING qual_all_Op opt_nulls_order ; +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); } @@ -11180,12 +11230,35 @@ 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 +func_expr: func_application within_group_clause filter_clause over_clause { - FuncCall *n = (FuncCall*)$1; - n->agg_filter = $2; - n->over = $3; - $$ = (Node*)n; + 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; } @@ -12744,6 +12817,7 @@ unreserved_keyword: | VIEW | VOLATILE | WHITESPACE_P + | WITHIN | WITHOUT | WORK | WRAPPER diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 98cb58a..23954a0 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -44,7 +44,9 @@ typedef struct int sublevels_up; } check_ungrouped_columns_context; -static int check_agg_arguments(ParseState *pstate, List *args, Expr *filter); +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,7 +77,8 @@ static bool check_ungrouped_columns_walker(Node *node, */ void transformAggregateCall(ParseState *pstate, Aggref *agg, - List *args, List *aggorder, bool agg_distinct) + List *args, List *aggorder, + bool agg_distinct, bool agg_within_group) { List *tlist; List *torder; @@ -93,12 +96,24 @@ transformAggregateCall(ParseState *pstate, Aggref *agg, */ tlist = NIL; attno = 1; - foreach(lc, args) + + if (agg_within_group) { - Expr *arg = (Expr *) lfirst(lc); - TargetEntry *tle = makeTargetEntry(arg, attno++, NULL, false); + 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); + tlist = lappend(tlist, tle); + } + + agg->isordset = FALSE; + agg->orddirectargs = NIL; } /* @@ -109,6 +124,11 @@ transformAggregateCall(ParseState *pstate, Aggref *agg, * * 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,7 +138,8 @@ transformAggregateCall(ParseState *pstate, Aggref *agg, &tlist, EXPR_KIND_ORDER_BY, true /* fix unknowns */ , - true /* force SQL99 rules */ ); + true /* force SQL99 rules */ , + agg_within_group /* keep duplicates? */ ); /* * If we have DISTINCT, transform that to produce a distinctList. @@ -160,7 +181,8 @@ 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); + 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,7 +334,7 @@ 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) +check_agg_arguments(ParseState *pstate, List *args, List *agg_ordset, Expr *filter) { int agglevel; check_agg_arguments_context context; @@ -330,6 +352,9 @@ check_agg_arguments(ParseState *pstate, List *args, Expr *filter) 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,8 +378,8 @@ 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)))); + locate_agg_of_level((Node *) args, + agglevel)))); return agglevel; } @@ -823,8 +848,16 @@ 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) + (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,3 +1075,98 @@ build_aggregate_fnexprs(Oid *agg_input_types, 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; +} diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 7a1261d..c445413 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -71,7 +71,8 @@ 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); + List **tlist, ParseExprKind exprKind, + bool keepDuplicates); static int get_matching_location(int sortgroupref, List *sortgrouprefs, List *exprs); static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle, @@ -1477,7 +1478,7 @@ findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist, /* * Otherwise, we have an expression, so process it per SQL99 rules. */ - return findTargetlistEntrySQL99(pstate, node, tlist, exprKind); + return findTargetlistEntrySQL99(pstate, node, tlist, exprKind, false); } /* @@ -1492,10 +1493,11 @@ 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 + * keepDuplicates if true, don't try and match to any existing entry */ static TargetEntry * findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist, - ParseExprKind exprKind) + ParseExprKind exprKind, bool keepDuplicates) { TargetEntry *target_result; ListCell *tl; @@ -1510,24 +1512,27 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist, */ expr = transformExpr(pstate, node, exprKind); - foreach(tl, *tlist) + if (!keepDuplicates) { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Node *texpr; + 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); + /* + * 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; + if (equal(expr, texpr)) + return tle; + } } /* @@ -1569,7 +1574,7 @@ transformGroupClause(ParseState *pstate, List *grouplist, if (useSQL99) tle = findTargetlistEntrySQL99(pstate, gexpr, - targetlist, exprKind); + targetlist, exprKind, false); else tle = findTargetlistEntrySQL92(pstate, gexpr, targetlist, exprKind); @@ -1636,11 +1641,14 @@ transformSortClause(ParseState *pstate, List **targetlist, ParseExprKind exprKind, bool resolveUnknown, - bool useSQL99) + bool useSQL99, + bool keepDuplicates) { List *sortlist = NIL; ListCell *olitem; + Assert(useSQL99 || !keepDuplicates); + foreach(olitem, orderlist) { SortBy *sortby = (SortBy *) lfirst(olitem); @@ -1648,7 +1656,7 @@ transformSortClause(ParseState *pstate, if (useSQL99) tle = findTargetlistEntrySQL99(pstate, sortby->node, - targetlist, exprKind); + targetlist, exprKind, keepDuplicates); else tle = findTargetlistEntrySQL92(pstate, sortby->node, targetlist, exprKind); @@ -1718,7 +1726,8 @@ transformWindowDefinitions(ParseState *pstate, targetlist, EXPR_KIND_WINDOW_ORDER, true /* fix unknowns */ , - true /* force SQL99 rules */ ); + true /* force SQL99 rules */, + false /* don't add duplicates */); partitionClause = transformGroupClause(pstate, windef->partitionClause, targetlist, diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c index fe57c59..f3499fc 100644 --- a/src/backend/parser/parse_collate.c +++ b/src/backend/parser/parse_collate.c @@ -73,7 +73,9 @@ typedef struct 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,44 +566,16 @@ 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 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; - 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_aggregate_collations(aggref, context, &loccontext); assign_expr_collations(context->pstate, - (Node *) aggref->aggfilter); + (Node *) aggref->aggfilter); } break; case T_WindowFunc: @@ -802,3 +776,159 @@ assign_collations_walker(Node *node, assign_collations_context *context) 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); + } + } +} diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 68b711d..1800a68 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -463,8 +463,8 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection) newresult = ParseFuncOrColumn(pstate, list_make1(n), list_make1(result), - NIL, NULL, false, false, false, - NULL, true, location); + location, + NULL); if (newresult == NULL) unknown_attribute(pstate, result, strVal(n), location); result = newresult; @@ -631,8 +631,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), - NIL, NULL, false, false, false, - NULL, true, cref->location); + cref->location, NULL); } break; } @@ -676,8 +675,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), - NIL, NULL, false, false, false, - NULL, true, cref->location); + cref->location, NULL); } break; } @@ -734,8 +732,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), - NIL, NULL, false, false, false, - NULL, true, cref->location); + cref->location, NULL); } break; } @@ -1242,38 +1239,20 @@ 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))); + 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); + fn->location, + fn); } static Node * diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index ede36d1..456396f 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -17,16 +17,19 @@ #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,15 +59,21 @@ 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. + * The argument expressions (in fargs) must have been transformed + * already. */ 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) + 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,6 +90,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, 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,6 +113,15 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, 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,6 +187,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, } } + 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,6 +200,26 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, } /* + * 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,6 +297,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, 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,6 +322,53 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, 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("function %s has %d ordering columns but %d hypothetical arguments", + NameListToString(funcname), list_length(agg_order), (nvargs - list_length(agg_order))), + 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,13 +454,16 @@ 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); + 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 && declared_arg_types[nargs - 1] != ANYOID) + if (nvargs > 0 && vatype != ANYOID) { ArrayExpr *newa = makeNode(ArrayExpr); int non_var_args = nargs - nvargs; @@ -388,16 +494,31 @@ 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. + * + * 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) { - Oid va_arr_typid = actual_arg_types[nargs - 1]; + 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 *) llast(fargs))))); + parser_errposition(pstate, + exprLocation((Node *) list_nth(fargs, nargs - 1 - ignore_args))))); } /* build the appropriate output structure */ @@ -421,6 +542,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, /* 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,14 +555,24 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, 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) + 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,7 +601,8 @@ 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); + transformAggregateCall(pstate, aggref, fargs, agg_order, + agg_distinct, agg_within_group); retval = (Node *) aggref; } @@ -473,6 +611,12 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, /* 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. */ @@ -1363,11 +1507,21 @@ func_get_detail(List *funcname, void make_fn_arguments(ParseState *pstate, List *fargs, + List *agg_order, Oid *actual_arg_types, - Oid *declared_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) { @@ -1375,6 +1529,7 @@ make_fn_arguments(ParseState *pstate, 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 @@ -1395,18 +1550,66 @@ 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; + /* + * 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++; + } } /* diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c index dd80fa9..08cbabd 100644 --- a/src/backend/parser/parse_oper.c +++ b/src/backend/parser/parse_oper.c @@ -823,7 +823,7 @@ 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); + make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false); /* and build the expression node */ result = makeNode(OpExpr); @@ -953,7 +953,7 @@ 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); + make_fn_arguments(pstate, args, NULL, actual_arg_types, declared_arg_types, false); /* and build the expression node */ result = makeNode(ScalarArrayOpExpr); diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 41a8982..8ab553e 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -19,13 +19,13 @@ 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 \ + 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 \ - ri_triggers.o pg_lzcompress.o pg_locale.o formatting.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 \ diff --git a/src/backend/utils/adt/hypotheticalset.c b/src/backend/utils/adt/hypotheticalset.c new file mode 100644 index 0000000..1c9fd81 --- /dev/null +++ b/src/backend/utils/adt/hypotheticalset.c @@ -0,0 +1,223 @@ +/*------------------------------------------------------------------------- + * + * 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 = 0.0; + + if (rowcount == 1) + PG_RETURN_FLOAT8(0); + + 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); +} diff --git a/src/backend/utils/adt/inversedistribution.c b/src/backend/utils/adt/inversedistribution.c new file mode 100644 index 0000000..fdda722 --- /dev/null +++ b/src/backend/utils/adt/inversedistribution.c @@ -0,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 = (Datum) 0; + bool last_val_is_mode = false; + int64 val_freq = 0; + Datum mode_val = (Datum) 0; + 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); +} diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 5ffce68..aa27fd0 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -22,6 +22,7 @@ #include "access/sysattr.h" #include "catalog/dependency.h" #include "catalog/indexing.h" +#include "catalog/pg_aggregate.h" #include "catalog/pg_authid.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -293,6 +294,9 @@ static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname, 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, @@ -403,6 +407,8 @@ static char *generate_function_name(Oid funcid, int nargs, 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 ") @@ -2268,6 +2274,149 @@ print_function_arguments(StringInfo buf, HeapTuple proctup, /* + * 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 @@ -7408,6 +7557,80 @@ static void 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; @@ -7462,14 +7685,6 @@ 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, ')'); } /* diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c index ea8af9f..7375d8e 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -1411,23 +1411,29 @@ 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. + * 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); - *should_free = false; + if (should_free) + *should_free = false; if (forward) { if (state->current < state->memtupcount) { - *stup = state->memtuples[state->current++]; + if (stup) + *stup = state->memtuples[state->current]; + state->current++; return true; } state->eof_reached = true; @@ -1459,21 +1465,25 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward, if (state->current <= 0) return false; } - *stup = state->memtuples[state->current - 1]; + if (stup) + *stup = state->memtuples[state->current - 1]; return true; } break; case TSS_SORTEDONTAPE: Assert(forward || state->randomAccess); - *should_free = true; + 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, stup, state->result_tape, tuplen); + READTUP(state, ptup, state->result_tape, tuplen); + if (!stup && dummy.tuple) + pfree(dummy.tuple); return true; } else @@ -1546,12 +1556,15 @@ 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); + READTUP(state, ptup, state->result_tape, tuplen); + if (!stup && dummy.tuple) + pfree(dummy.tuple); return true; case TSS_FINALMERGE: Assert(forward); - *should_free = true; + if (should_free) + *should_free = true; /* * This code should match the inner loop of mergeonerun(). @@ -1563,11 +1576,11 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward, int tupIndex; SortTuple *newtup; - *stup = state->memtuples[0]; + *ptup = state->memtuples[0]; /* returned tuple is no longer counted in our memory space */ - if (stup->tuple) + if (ptup->tuple) { - tuplen = GetMemoryChunkSpace(stup->tuple); + tuplen = GetMemoryChunkSpace(ptup->tuple); state->availMem += tuplen; state->mergeavailmem[srcTape] += tuplen; } @@ -1598,6 +1611,8 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward, newtup->tupindex = state->mergefreelist; state->mergefreelist = tupIndex; state->mergeavailslots[srcTape]++; + if (!stup && dummy.tuple) + pfree(dummy.tuple); return true; } return false; @@ -1620,20 +1635,22 @@ tuplesort_gettupleslot(Tuplesortstate *state, bool forward, MemoryContext oldcontext = MemoryContextSwitchTo(state->sortcontext); SortTuple stup; bool should_free; + bool found; - if (!tuplesort_gettuple_common(state, forward, &stup, &should_free)) - stup.tuple = NULL; + found = tuplesort_gettuple_common(state, forward, (slot ? &stup : NULL), &should_free); MemoryContextSwitchTo(oldcontext); - if (stup.tuple) + if (found) { - ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free); + if (slot) + ExecStoreMinimalTuple((MinimalTuple) stup.tuple, slot, should_free); return true; } else { - ExecClearTuple(slot); + if (slot) + ExecClearTuple(slot); return false; } } @@ -1692,24 +1709,27 @@ tuplesort_getdatum(Tuplesortstate *state, bool forward, SortTuple stup; bool should_free; - if (!tuplesort_gettuple_common(state, forward, &stup, &should_free)) + if (!tuplesort_gettuple_common(state, forward, (val ? &stup : NULL), &should_free)) { MemoryContextSwitchTo(oldcontext); return false; } - if (stup.isnull1 || state->datumTypeByVal) + if (val) { - *val = stup.datum1; - *isNull = stup.isnull1; - } - else - { - if (should_free) + if (stup.isnull1 || state->datumTypeByVal) + { *val = stup.datum1; + *isNull = stup.isnull1; + } else - *val = datumCopy(stup.datum1, false, state->datumTypeLen); - *isNull = false; + { + if (should_free) + *val = stup.datum1; + else + *val = datumCopy(stup.datum1, false, state->datumTypeLen); + *isNull = false; + } } MemoryContextSwitchTo(oldcontext); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 769058d..c70fb1a 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -229,6 +229,7 @@ static void getTableData(TableInfo *tblinfo, int numTables, bool oids); 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, @@ -9439,6 +9440,22 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang) } /* + * 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 @@ -11493,15 +11510,22 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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) @@ -11517,11 +11541,31 @@ dumpAgg(Archive *fout, AggInfo *agginfo) selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name); /* Get aggregate-specific details */ - if (fout->remoteVersion >= 80400) + 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, " @@ -11536,6 +11580,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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 " @@ -11548,6 +11595,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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 " @@ -11560,6 +11610,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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 " @@ -11572,6 +11625,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo) "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 " @@ -11584,6 +11640,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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"); @@ -11591,11 +11650,25 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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 >= 80400) + 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; @@ -11625,36 +11698,58 @@ 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); + /* + * 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, " SFUNC = %s,\n STYPE = %s", + appendPQExpBuffer(details, "\n SFUNC = %s,\n STYPE = %s", fmtId(aggtransfn), aggtranstype); } else { /* need quotes all around */ - appendPQExpBuffer(details, " SFUNC = %s,\n", + appendPQExpBuffer(details, "\n SFUNC = %s,\n", fmtId(aggtransfn)); appendPQExpBuffer(details, " STYPE = %s", fmtId(aggtranstype)); } - if (!PQgetisnull(res, 0, i_agginitval)) + if (strcmp(aggfinalfn, "-") != 0) { - appendPQExpBuffer(details, ",\n INITCOND = "); - appendStringLiteralAH(details, agginitval, fout); + appendPQExpBuffer(details, "%s\n FINALFUNC = %s", + (has_comma ? "" : ","), + aggfinalfn); } - if (strcmp(aggfinalfn, "-") != 0) + if (hypothetical) + appendPQExpBuffer(details, ",\n HYPOTHETICAL"); + + if (isstrict) + appendPQExpBuffer(details, ",\n STRICT"); + + if (!PQgetisnull(res, 0, i_agginitval)) { - appendPQExpBuffer(details, ",\n FINALFUNC = %s", - aggfinalfn); + appendPQExpBuffer(details, ",\n INITCOND = "); + appendStringLiteralAH(details, agginitval, fout); } aggsortop = convertOperatorReference(fout, aggsortop); @@ -11664,6 +11759,13 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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 */ @@ -11671,7 +11773,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo) fmtId(agginfo->aggfn.dobj.namespace->dobj.name), aggsig); - appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n", + appendPQExpBuffer(q, "CREATE AGGREGATE %s (%s\n);\n", aggfullsig, details->data); appendPQExpBuffer(labelq, "AGGREGATE %s", aggsig); @@ -11700,7 +11802,7 @@ 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. + * syntax for zero-argument aggregates and ordered set functions. */ free(aggsig); free(aggsig_tag); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index ceda13e..df6be07 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -72,7 +72,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem) gettext_noop("Name"), gettext_noop("Result data type")); - if (pset.sversion >= 80400) + 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,7 +258,26 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool gettext_noop("Schema"), gettext_noop("Name")); - if (pset.sversion >= 80400) + 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" diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h index 5ad6ea6..b546ca4 100644 --- a/src/include/catalog/pg_aggregate.h +++ b/src/include/catalog/pg_aggregate.h @@ -32,6 +32,9 @@ * 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,6 +47,9 @@ CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS regproc aggfinalfn; Oid aggsortop; Oid aggtranstype; + Oid aggtranssortop; + int32 aggordnargs; + bool aggisordsetfunc; #ifdef CATALOG_VARLEN /* variable-length fields start here */ text agginitval; @@ -62,13 +68,16 @@ typedef FormData_pg_aggregate *Form_pg_aggregate; * ---------------- */ -#define Natts_pg_aggregate 6 +#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_agginitval 6 +#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,163 +86,176 @@ 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}" )); +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 _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_ )); +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 _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_ )); +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 _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_ )); +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" )); -DATA(insert ( 2803 int8inc - 0 20 "0" )); +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,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}" )); +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,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}" )); +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,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}" )); +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,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}" )); +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,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}" )); +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,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}" )); +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" )); -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}" )); +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 _null_ )); -DATA(insert ( 2518 boolor_statefunc - 59 16 _null_ )); -DATA(insert ( 2519 booland_statefunc - 58 16 _null_ )); +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 _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_ )); +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 _null_ )); +DATA(insert ( 2901 xmlconcat2 - 0 142 0 -1 f _null_ )); /* array */ -DATA(insert ( 2335 array_agg_transfn array_agg_finalfn 0 2281 _null_ )); +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 _null_ )); +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 _null_ )); +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 _null_ )); +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 ( 3920 - 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,6 +263,7 @@ DATA(insert ( 3175 json_agg_transfn json_agg_finalfn 0 2281 _null_ )); extern Oid AggregateCreate(const char *aggName, Oid aggNamespace, int numArgs, + int numDirectArgs, oidvector *parameterTypes, Datum allParameterTypes, Datum parameterModes, @@ -249,7 +272,11 @@ extern Oid AggregateCreate(const char *aggName, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, + List *aggtranssortopName, Oid aggTransType, - const char *agginitval); + const char *agginitval, + bool isStrict, + bool isOrderedSetFunc, + bool isHypotheticalSet); #endif /* PG_AGGREGATE_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index ca4fc62..a0d2e12 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1973,6 +1973,10 @@ DATA(insert OID = 2232 ( pg_get_function_identity_arguments PGNSP PGUID 12 1 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"); @@ -4750,6 +4754,74 @@ DESCR("SP-GiST support for quad tree over range"); /* 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 = 3920 ( 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, diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 1f72e1b..50ee3de 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -651,6 +651,35 @@ extern void **find_rendezvous_variable(const char *varName); 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 diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index bedcf04..b94e57c 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -588,6 +588,7 @@ typedef struct AggrefExprState { 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; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index fc6b1d7..f0a020d 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -426,7 +426,8 @@ 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 */ + T_FdwRoutine, /* in foreign/fdwapi.h */ + T_AggStatePerAggData /* private in nodeAgg.c */ } NodeTag; /* diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 55524b4..e8e0ff0 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -303,6 +303,7 @@ typedef struct FuncCall 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; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 7918537..594dddf 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -247,9 +247,12 @@ typedef struct Aggref 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; diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 8bd34d6..ab27156 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -412,6 +412,7 @@ PG_KEYWORD("where", WHERE, RESERVED_KEYWORD) 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) diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h index b6d9dd3..5fe6295 100644 --- a/src/include/parser/parse_agg.h +++ b/src/include/parser/parse_agg.h @@ -17,7 +17,7 @@ extern void transformAggregateCall(ParseState *pstate, Aggref *agg, List *args, List *aggorder, - bool agg_distinct); + bool agg_distinct, bool agg_within_group); extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, WindowDef *windef); @@ -34,4 +34,18 @@ extern void build_aggregate_fnexprs(Oid *agg_input_types, 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 */ diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h index 9bdb033..dcc08d0 100644 --- a/src/include/parser/parse_clause.h +++ b/src/include/parser/parse_clause.h @@ -31,7 +31,7 @@ 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); + bool resolveUnknown, bool useSQL99, bool keepDuplicates); extern List *transformWindowDefinitions(ParseState *pstate, List *windowdefs, diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h index d33eef3..834fc92 100644 --- a/src/include/parser/parse_func.h +++ b/src/include/parser/parse_func.h @@ -38,14 +38,11 @@ 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 */ + 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); + int location, FuncCall *fn); extern FuncDetailCode func_get_detail(List *funcname, List *fargs, List *fargnames, @@ -66,8 +63,10 @@ extern FuncCandidateList func_select_candidate(int nargs, extern void make_fn_arguments(ParseState *pstate, List *fargs, + List *agg_order, Oid *actual_arg_types, - Oid *declared_arg_types); + Oid *declared_arg_types, + bool requiresUnification); extern const char *funcname_signature_string(const char *funcname, int nargs, List *argnames, const Oid *argtypes); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index ce3f00b..8cda3d9 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -665,6 +665,8 @@ extern Datum pg_get_functiondef(PG_FUNCTION_ARGS); 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); diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 298b2e4..a97834b 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -1249,6 +1249,220 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1) {"(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: function rank has 2 ordering columns but 1 hypothetical arguments +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) + +-- divide by zero check +select percent_rank(0) within group (order by x) from generate_series(1,0) x; + percent_rank +-------------- + 0 +(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 diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 57d614f..c72bd78 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -700,9 +700,17 @@ SELECT * FROM funcdescs -- **************** 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 aggtransfn = 0 OR aggtranstype = 0; +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,8 +772,9 @@ 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])); + OR (aggisordsetfunc IS FALSE + AND (pfn.pronargs != 1 + OR NOT binary_coercible(a.aggtranstype, pfn.proargtypes[0])))); aggfnoid | proname | oid | proname ----------+---------+-----+--------- (0 rows) @@ -857,10 +866,12 @@ 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; +-- 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) diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index 397edff..91d626c 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -481,6 +481,72 @@ select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1) 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; + +-- divide by zero check +select percent_rank(0) within group (order by x) from generate_series(1,0) 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; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index efcd70f..0b2cba7 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -564,10 +564,18 @@ SELECT * FROM funcdescs -- **************** 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 aggtransfn = 0 OR aggtranstype = 0; +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,8 +626,9 @@ 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])); + 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,11 +694,13 @@ 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. +-- 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 oid, proname -FROM pg_proc AS p -WHERE proisagg AND provariadic != 0; +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.