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 @@
aggtransfnregprocpg_proc.oid
- Transition function
+ Transition function (zero if none)aggfinalfn
@@ -370,7 +370,25 @@
aggtranstypeoidpg_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 notagginitval
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.