+ type
s they operate on. If
class="parameter">pattern
is specified, only aggregates whose names match the pattern are shown.
-
+
SQL
-
Aggregate Operators
+
Aggregate Functions
-
SQL provides aggregate
operators (e.g. AVG,
- COUNT, SUM, MIN, MAX) that take an expression as argument. The
- expression is evaluated at each row that satisfies the WHERE
- clause, and the aggregate operator is calculated over this set
+
SQL provides aggregate
functions such as AVG,
+ COUNT, SUM, MIN, and MAX. The argument(s) of an aggregate function
+ are evaluated at each row that satisfies the WHERE
+ clause, and the aggregate function is calculated over this set
of input values. Normally, an aggregate delivers a single
result for a whole SELECT statement. But if
grouping is specified in the query, then a separate calculation
SQL allows one to partition the tuples of a table
into groups. Then the
- aggregate operators described above can be applied to the groups —
- i.e. the value of the aggregate operator is no longer calculated over
+ aggregate functions described above can be applied to the groups —
+ i.e. the value of the aggregate function is no longer calculated over
all the values of the specified column but over all values of a
- group. Thus the aggregate operator is evaluated separately for every
+ group. Thus the aggregate function is evaluated separately for every
group.
In our example we got four groups and now we can apply the aggregate
- operator COUNT to every group leading to the final result of the query
+ function COUNT to every group leading to the final result of the query
given above.
Note that for a query using GROUP BY and aggregate
- operators to make sense the target list can only refer directly to
+ functions to make sense, the target list can only refer directly to
the attributes being grouped by. Other attributes may only be used
- inside the argument of an aggregate function. Otherwise there would
+ inside the arguments of aggregate functions. Otherwise there would
not be a unique value to associate with the other attributes.
-
+
SQL Syntax
The asterisk (*) is used in some contexts to denote
all the fields of a table row or composite value. It also
- has a special meaning when used as the argument of the
- COUNT aggregate function.
+ has a special meaning when used as the argument of an
+ aggregate function, namely that the aggregate does not require
+ any explicit parameter.
syntax of an aggregate expression is one of the following:
-aggregate_name (expression)
-aggregate_name (ALL expression)
-aggregate_name (DISTINCT expression)
+aggregate_name (expression [ , ... ] )
+aggregate_name (ALL expression [ , ... ] )
+aggregate_name (DISTINCT expression [ , ... ] )
aggregate_name ( * )
The first form of aggregate expression invokes the aggregate
- across all input rows for which the given expression yields a
- non-null value. (Actually, it is up to the aggregate function
+ across all input rows for which the given expression(s) yield
+ non-null values. (Actually, it is up to the aggregate function
whether to ignore null values or not — but all the standard ones do.)
The second form is the same as the first, since
ALL is the default. The third form invokes the
- aggregate for all distinct non-null values of the expression found
+ aggregate for all distinct non-null values of the expressions found
in the input rows. The last form invokes the aggregate once for
each input row regardless of null or non-null values; since no
particular input value is specified, it is generally only useful
- for the count() aggregate function.
+ for the count(*) aggregate function.
and
), the aggregate is normally
evaluated over the rows of the subquery. But an exception occurs
- if the aggregate's argument contains only outer-level variables:
+ if the aggregate's arguments contain only outer-level variables:
the aggregate then belongs to the nearest such outer level, and is
evaluated over the rows of that query. The aggregate expression
as a whole is then an outer reference for the subquery it appears in,
appearing only in the result list or HAVING> clause
applies with respect to the query level that the aggregate belongs to.
+
+
+
PostgreSQL currently does not support
+ DISTINCT> with more than one input expression.
+
+
-
+
User-Defined Aggregates
Aggregate functions in
PostgreSQL
- are expressed as state values
+ are expressed in terms of state values
and state transition functions.
- That is, an aggregate can be
- defined in terms of state that is modified whenever an
- input item is processed. To define a new aggregate
+ 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
Another bit of default behavior for a strict> transition function
is that the previous state value is retained unchanged whenever a
null input value is encountered. Thus, null values are ignored. If you
- need some other behavior for null inputs, just do not define your transition
- function as strict, and code it to test for null inputs and do
- whatever is needed.
+ need some other behavior for null inputs, do not declare your
+ transition function as strict; instead code it to test for null inputs and
+ do whatever is needed.
- avg> (average) is a more complex example of an aggregate. It requires
+ avg> (average) is a more complex example of an aggregate.
+ It requires
two pieces of running state: the sum of the inputs and the count
of the number of inputs. The final result is obtained by dividing
these quantities. Average is typically implemented by using a
See
for an explanation of polymorphic functions.
Going a step further, the aggregate function itself may be specified
- with a polymorphic input type and state type, allowing a single
+ with polymorphic input type(s) and state type, allowing a single
aggregate definition to serve for multiple input data types.
Here is an example of a polymorphic aggregate:
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.81 2006/07/14 14:52:17 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.82 2006/07/27 19:52:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
void
AggregateCreate(const char *aggName,
Oid aggNamespace,
- Oid aggBaseType,
+ Oid *aggArgTypes,
+ int numArgs,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
Oid transfn;
Oid finalfn = InvalidOid; /* can be omitted */
Oid sortop = InvalidOid; /* can be omitted */
+ bool hasPolyArg;
Oid rettype;
Oid finaltype;
- Oid fnArgs[2]; /* we only deal with 1- and 2-arg fns */
+ Oid *fnArgs;
int nargs_transfn;
Oid procOid;
TupleDesc tupDesc;
if (!aggtransfnName)
elog(ERROR, "aggregate must have a transition function");
+ /* check for polymorphic arguments */
+ hasPolyArg = false;
+ for (i = 0; i < numArgs; i++)
+ {
+ if (aggArgTypes[i] == ANYARRAYOID ||
+ aggArgTypes[i] == ANYELEMENTOID)
+ {
+ hasPolyArg = true;
+ break;
+ }
+ }
+
/*
- * If transtype is polymorphic, basetype must be polymorphic also; else we
- * will have no way to deduce the actual transtype.
+ * If transtype is polymorphic, must have polymorphic argument also;
+ * else we will have no way to deduce the actual transtype.
*/
- if ((aggTransType == ANYARRAYOID || aggTransType == ANYELEMENTOID) &&
- !(aggBaseType == ANYARRAYOID || aggBaseType == ANYELEMENTOID))
+ if (!hasPolyArg &&
+ (aggTransType == ANYARRAYOID || aggTransType == ANYELEMENTOID))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot determine transition data type"),
- errdetail("An aggregate using \"anyarray\" or \"anyelement\" as "
- "transition type must have one of them as its base type.")));
+ errdetail("An aggregate using \"anyarray\" or \"anyelement\" as transition type must have at least one argument of either type.")));
- /* handle transfn */
+ /* find the transfn */
+ nargs_transfn = numArgs + 1;
+ fnArgs = (Oid *) palloc(nargs_transfn * sizeof(Oid));
fnArgs[0] = aggTransType;
- if (aggBaseType == ANYOID)
- nargs_transfn = 1;
- else
- {
- fnArgs[1] = aggBaseType;
- nargs_transfn = 2;
- }
+ memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs,
&rettype);
proc = (Form_pg_proc) GETSTRUCT(tup);
/*
- * If the transfn is strict and the initval is NULL, make sure input type
- * and transtype are the same (or at least binary-compatible), so that
+ * 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 (!IsBinaryCoercible(aggBaseType, aggTransType))
+ 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")));
Assert(OidIsValid(finaltype));
/*
- * If finaltype (i.e. aggregate return type) is polymorphic, basetype must
+ * If finaltype (i.e. aggregate return type) is polymorphic, inputs must
* be polymorphic also, else parser will fail to deduce result type.
- * (Note: given the previous test on transtype and basetype, this cannot
+ * (Note: given the previous test on transtype and inputs, this cannot
* happen, unless someone has snuck a finalfn definition into the catalogs
* that itself violates the rule against polymorphic result with no
* polymorphic input.)
*/
- if ((finaltype == ANYARRAYOID || finaltype == ANYELEMENTOID) &&
- !(aggBaseType == ANYARRAYOID || aggBaseType == ANYELEMENTOID))
+ if (!hasPolyArg &&
+ (finaltype == ANYARRAYOID || finaltype == ANYELEMENTOID))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot determine result data type"),
errdetail("An aggregate returning \"anyarray\" or \"anyelement\" "
- "must have one of them as its base type.")));
+ "must have at least one argument of either type.")));
/* handle sortop, if supplied */
if (aggsortopName)
+ {
+ if (numArgs != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("sort operator can only be specified for single-argument aggregates")));
sortop = LookupOperName(NULL, aggsortopName,
- aggBaseType, aggBaseType,
+ aggArgTypes[0], aggArgTypes[0],
false, -1);
+ }
/*
* Everything looks okay. Try to create the pg_proc entry for the
* aggregate. (This could fail if there's already a conflicting entry.)
*/
- fnArgs[0] = aggBaseType;
procOid = ProcedureCreate(aggName,
aggNamespace,
false, /* isStrict (not needed for agg) */
PROVOLATILE_IMMUTABLE, /* volatility (not
* needed for agg) */
- buildoidvector(fnArgs, 1), /* paramTypes */
+ buildoidvector(aggArgTypes,
+ numArgs), /* paramTypes */
PointerGetDatum(NULL), /* allParamTypes */
PointerGetDatum(NULL), /* parameterModes */
PointerGetDatum(NULL)); /* parameterNames */
Oid *true_oid_array;
FuncDetailCode fdresult;
AclResult aclresult;
+ int i;
+ bool allPolyArgs = true;
/*
* func_get_detail looks up the function in the catalogs, does
* If the given type(s) are all polymorphic, there's nothing we can check.
* Otherwise, enforce consistency, and possibly refine the result type.
*/
- if ((input_types[0] == ANYARRAYOID || input_types[0] == ANYELEMENTOID) &&
- (nargs == 1 ||
- (input_types[1] == ANYARRAYOID || input_types[1] == ANYELEMENTOID)))
+ for (i = 0; i < nargs; i++)
{
- /* nothing to check here */
+ if (input_types[i] != ANYARRAYOID &&
+ input_types[i] != ANYELEMENTOID)
+ {
+ allPolyArgs = false;
+ break;
+ }
}
- else
+
+ if (!allPolyArgs)
{
*rettype = enforce_generic_type_consistency(input_types,
true_oid_array,
* func_get_detail will find functions requiring run-time argument type
* coercion, but nodeAgg.c isn't prepared to deal with that
*/
- if (true_oid_array[0] != ANYARRAYOID &&
- true_oid_array[0] != ANYELEMENTOID &&
- !IsBinaryCoercible(input_types[0], true_oid_array[0]))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("function %s requires run-time type coercion",
- func_signature_string(fnName, nargs, true_oid_array))));
-
- if (nargs == 2 &&
- true_oid_array[1] != ANYARRAYOID &&
- true_oid_array[1] != ANYELEMENTOID &&
- !IsBinaryCoercible(input_types[1], true_oid_array[1]))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("function %s requires run-time type coercion",
- func_signature_string(fnName, nargs, true_oid_array))));
+ for (i = 0; i < nargs; i++)
+ {
+ if (true_oid_array[i] != ANYARRAYOID &&
+ true_oid_array[i] != ANYELEMENTOID &&
+ !IsBinaryCoercible(input_types[i], true_oid_array[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function %s requires run-time type coercion",
+ func_signature_string(fnName, nargs, true_oid_array))));
+ }
/* Check aggregate creator has permission to call the function */
aclresult = pg_proc_aclcheck(fnOid, GetUserId(), ACL_EXECUTE);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/aggregatecmds.c,v 1.37 2006/07/14 14:52:18 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/aggregatecmds.c,v 1.38 2006/07/27 19:52:04 tgl Exp $
*
* DESCRIPTION
* The "DefineFoo" routines take the parse tree and pick out the
TypeName *baseType = NULL;
TypeName *transType = NULL;
char *initval = NULL;
- Oid baseTypeId;
+ Oid *aggArgTypes;
+ int numArgs;
Oid transTypeId;
ListCell *pl;
errmsg("aggregate sfunc must be specified")));
/*
- * look up the aggregate's input datatype.
+ * look up the aggregate's input datatype(s).
*/
if (oldstyle)
{
/*
- * Old style: use basetype parameter. This supports only one input.
+ * Old style: use basetype parameter. This supports aggregates
+ * of zero or one input, with input type ANY meaning zero inputs.
*
* Historically we allowed the command to look like basetype = 'ANY'
* so we must do a case-insensitive comparison for the name ANY. Ugh.
errmsg("aggregate input type must be specified")));
if (pg_strcasecmp(TypeNameToString(baseType), "ANY") == 0)
- baseTypeId = ANYOID;
+ {
+ numArgs = 0;
+ aggArgTypes = NULL;
+ }
else
- baseTypeId = typenameTypeId(NULL, baseType);
+ {
+ numArgs = 1;
+ aggArgTypes = (Oid *) palloc(sizeof(Oid));
+ aggArgTypes[0] = typenameTypeId(NULL, baseType);
+ }
}
else
{
/*
- * New style: args is a list of TypeNames. For the moment, though,
- * we allow at most one.
+ * New style: args is a list of TypeNames (possibly zero of 'em).
*/
+ ListCell *lc;
+ int i = 0;
+
if (baseType != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("basetype is redundant with aggregate input type specification")));
- if (args == NIL)
- {
- /* special case for agg(*) */
- baseTypeId = ANYOID;
- }
- else if (list_length(args) != 1)
+ numArgs = list_length(args);
+ aggArgTypes = (Oid *) palloc(sizeof(Oid) * numArgs);
+ foreach(lc, args)
{
- /* temporarily reject > 1 arg */
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("aggregates can have only one input")));
- baseTypeId = InvalidOid; /* keep compiler quiet */
- }
- else
- {
- baseTypeId = typenameTypeId(NULL, (TypeName *) linitial(args));
+ TypeName *curTypeName = (TypeName *) lfirst(lc);
+
+ aggArgTypes[i++] = typenameTypeId(NULL, curTypeName);
}
}
*/
AggregateCreate(aggName, /* aggregate name */
aggNamespace, /* namespace */
- baseTypeId, /* type of data being aggregated */
+ aggArgTypes, /* input data type(s) */
+ numArgs,
transfuncName, /* step function name */
finalfuncName, /* final function name */
sortoperatorName, /* sort operator name */
/* Look up function and make sure it's an aggregate */
procOid = LookupAggNameTypeNames(aggName, aggArgs, stmt->missing_ok);
-
+
if (!OidIsValid(procOid))
{
/* we only get here if stmt->missing_ok is true */
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.192 2006/07/14 14:52:19 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.193 2006/07/27 19:52:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
aggstate->aggs = lcons(astate, aggstate->aggs);
naggs = ++aggstate->numaggs;
- astate->target = ExecInitExpr(aggref->target, parent);
+ astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
+ parent);
/*
- * Complain if the aggregate's argument contains any
+ * Complain if the aggregate's arguments contain any
* aggregates; nested agg functions are semantically
* nonsensical. (This should have been caught earlier,
* but we defend against it here anyway.)
* ExecAgg evaluates each aggregate in the following steps:
*
* transvalue = initcond
- * foreach input_value do
- * transvalue = transfunc(transvalue, input_value)
+ * foreach input_tuple do
+ * transvalue = transfunc(transvalue, input_value(s))
* result = finalfunc(transvalue)
*
* If a finalfunc is not supplied then the result is just the ending
* If transfunc is marked "strict" in pg_proc and initcond is NULL,
* then the first non-NULL input_value is assigned directly to transvalue,
* and transfunc isn't applied until the second non-NULL input_value.
- * The agg's input type and transtype must be the same in this case!
+ * The agg's first input type and transtype must be the same in this case!
*
* If transfunc is marked "strict" then NULL input_values are skipped,
* keeping the previous transvalue. If transfunc is not strict then it
* is called for every input tuple and must deal with NULL initcond
- * or NULL input_value for itself.
+ * or NULL input_values for itself.
*
* If finalfunc is marked "strict" then it is not called when the
* ending transvalue is NULL, instead a NULL result is created
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.144 2006/07/14 14:52:19 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.145 2006/07/27 19:52:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
AggrefExprState *aggrefstate;
Aggref *aggref;
+ /* number of input arguments for aggregate */
+ int numArguments;
+
/* Oids of transfer functions */
Oid transfn_oid;
Oid finalfn_oid; /* may be InvalidOid */
static void advance_transition_function(AggState *aggstate,
AggStatePerAgg peraggstate,
AggStatePerGroup pergroupstate,
- Datum newVal, bool isNull);
+ FunctionCallInfoData *fcinfo);
static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
static void process_sorted_aggregate(AggState *aggstate,
AggStatePerAgg peraggstate,
}
/*
- * Given a new input value, advance the transition function of an aggregate.
+ * Given new input value(s), advance the transition function of an aggregate.
+ *
+ * The new values (and null flags) have been preloaded into argument positions
+ * 1 and up in fcinfo, so that we needn't copy them again to pass to the
+ * transition function. No other fields of fcinfo are assumed valid.
*
* It doesn't matter which memory context this is called in.
*/
advance_transition_function(AggState *aggstate,
AggStatePerAgg peraggstate,
AggStatePerGroup pergroupstate,
- Datum newVal, bool isNull)
+ FunctionCallInfoData *fcinfo)
{
- FunctionCallInfoData fcinfo;
+ int numArguments = peraggstate->numArguments;
MemoryContext oldContext;
+ Datum newVal;
+ int i;
if (peraggstate->transfn.fn_strict)
{
/*
- * For a strict transfn, nothing happens at a NULL input tuple; we
- * just keep the prior transValue.
+ * For a strict transfn, nothing happens when there's a NULL input;
+ * we just keep the prior transValue.
*/
- if (isNull)
- return;
+ for (i = 1; i <= numArguments; i++)
+ {
+ if (fcinfo->argnull[i])
+ return;
+ }
if (pergroupstate->noTransValue)
{
/*
* do not need to pfree the old transValue, since it's NULL.
*/
oldContext = MemoryContextSwitchTo(aggstate->aggcontext);
- pergroupstate->transValue = datumCopy(newVal,
+ pergroupstate->transValue = datumCopy(fcinfo->arg[1],
peraggstate->transtypeByVal,
peraggstate->transtypeLen);
pergroupstate->transValueIsNull = false;
/*
* OK to call the transition function
*/
- InitFunctionCallInfoData(fcinfo, &(peraggstate->transfn), 2,
+ InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn),
+ numArguments + 1,
(void *) aggstate, NULL);
- fcinfo.arg[0] = pergroupstate->transValue;
- fcinfo.argnull[0] = pergroupstate->transValueIsNull;
- fcinfo.arg[1] = newVal;
- fcinfo.argnull[1] = isNull;
+ fcinfo->arg[0] = pergroupstate->transValue;
+ fcinfo->argnull[0] = pergroupstate->transValueIsNull;
- newVal = FunctionCallInvoke(&fcinfo);
+ newVal = FunctionCallInvoke(fcinfo);
/*
* If pass-by-ref datatype, must copy the new value into aggcontext and
if (!peraggstate->transtypeByVal &&
DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue))
{
- if (!fcinfo.isnull)
+ if (!fcinfo->isnull)
{
MemoryContextSwitchTo(aggstate->aggcontext);
newVal = datumCopy(newVal,
}
pergroupstate->transValue = newVal;
- pergroupstate->transValueIsNull = fcinfo.isnull;
+ pergroupstate->transValueIsNull = fcinfo->isnull;
MemoryContextSwitchTo(oldContext);
}
for (aggno = 0; aggno < aggstate->numaggs; aggno++)
{
- AggStatePerAgg peraggstate = &aggstate->peragg[aggno];
- AggStatePerGroup pergroupstate = &pergroup[aggno];
- AggrefExprState *aggrefstate = peraggstate->aggrefstate;
- Aggref *aggref = peraggstate->aggref;
- Datum newVal;
- bool isNull;
+ AggStatePerAgg peraggstate = &aggstate->peragg[aggno];
+ AggStatePerGroup pergroupstate = &pergroup[aggno];
+ AggrefExprState *aggrefstate = peraggstate->aggrefstate;
+ Aggref *aggref = peraggstate->aggref;
+ FunctionCallInfoData fcinfo;
+ int i;
+ ListCell *arg;
+ MemoryContext oldContext;
- newVal = ExecEvalExprSwitchContext(aggrefstate->target, econtext,
- &isNull, NULL);
+ /* Switch memory context just once for all args */
+ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+ /* Evaluate inputs and save in fcinfo */
+ /* We start from 1, since the 0th arg will be the transition value */
+ i = 1;
+ foreach(arg, aggrefstate->args)
+ {
+ ExprState *argstate = (ExprState *) lfirst(arg);
+
+ fcinfo.arg[i] = ExecEvalExpr(argstate, econtext,
+ fcinfo.argnull + i, NULL);
+ i++;
+ }
+
+ /* Switch back */
+ MemoryContextSwitchTo(oldContext);
if (aggref->aggdistinct)
{
/* in DISTINCT mode, we may ignore nulls */
- if (isNull)
+ /* XXX we assume there is only one input column */
+ if (fcinfo.argnull[1])
continue;
- tuplesort_putdatum(peraggstate->sortstate, newVal, isNull);
+ tuplesort_putdatum(peraggstate->sortstate, fcinfo.arg[1],
+ fcinfo.argnull[1]);
}
else
{
advance_transition_function(aggstate, peraggstate, pergroupstate,
- newVal, isNull);
+ &fcinfo);
}
}
}
bool haveOldVal = false;
MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
MemoryContext oldContext;
- Datum newVal;
- bool isNull;
+ Datum *newVal;
+ bool *isNull;
+ FunctionCallInfoData fcinfo;
tuplesort_performsort(peraggstate->sortstate);
+ newVal = fcinfo.arg + 1;
+ isNull = fcinfo.argnull + 1;
+
/*
* Note: if input type is pass-by-ref, the datums returned by the sort are
* freshly palloc'd in the per-query context, so we must be careful to
*/
while (tuplesort_getdatum(peraggstate->sortstate, true,
- &newVal, &isNull))
+ newVal, isNull))
{
/*
* DISTINCT always suppresses nulls, per SQL spec, regardless of the
* transition function's strictness.
*/
- if (isNull)
+ if (*isNull)
continue;
/*
if (haveOldVal &&
DatumGetBool(FunctionCall2(&peraggstate->equalfn,
- oldVal, newVal)))
+ oldVal, *newVal)))
{
/* equal to prior, so forget this one */
if (!peraggstate->inputtypeByVal)
- pfree(DatumGetPointer(newVal));
+ pfree(DatumGetPointer(*newVal));
}
else
{
advance_transition_function(aggstate, peraggstate, pergroupstate,
- newVal, false);
+ &fcinfo);
/* forget the old value, if any */
if (haveOldVal && !peraggstate->inputtypeByVal)
pfree(DatumGetPointer(oldVal));
/* and remember the new one for subsequent equality checks */
- oldVal = newVal;
+ oldVal = *newVal;
haveOldVal = true;
}
AggrefExprState *aggrefstate = (AggrefExprState *) lfirst(l);
Aggref *aggref = (Aggref *) aggrefstate->xprstate.expr;
AggStatePerAgg peraggstate;
- Oid inputType;
+ Oid inputTypes[FUNC_MAX_ARGS];
+ int numArguments;
HeapTuple aggTuple;
Form_pg_aggregate aggform;
Oid aggtranstype;
*finalfnexpr;
Datum textInitVal;
int i;
+ ListCell *lc;
/* Planner should have assigned aggregate to correct level */
Assert(aggref->agglevelsup == 0);
/* Fill in the peraggstate data */
peraggstate->aggrefstate = aggrefstate;
peraggstate->aggref = aggref;
+ numArguments = list_length(aggref->args);
+ peraggstate->numArguments = numArguments;
/*
- * Get actual datatype of the input. We need this because it may be
- * different from the agg's declared input type, when the agg accepts
- * ANY (eg, COUNT(*)) or ANYARRAY or ANYELEMENT.
+ * Get actual datatypes of the inputs. These could be different
+ * from the agg's declared input types, when the agg accepts ANY,
+ * ANYARRAY or ANYELEMENT.
*/
- inputType = exprType((Node *) aggref->target);
+ i = 0;
+ foreach(lc, aggref->args)
+ {
+ inputTypes[i++] = exprType((Node *) lfirst(lc));
+ }
aggTuple = SearchSysCache(AGGFNOID,
ObjectIdGetDatum(aggref->aggfnoid),
aggtranstype = aggform->aggtranstype;
if (aggtranstype == ANYARRAYOID || aggtranstype == ANYELEMENTOID)
{
- /* have to fetch the agg's declared input type... */
- Oid *agg_arg_types;
+ /* have to fetch the agg's declared input types... */
+ Oid *declaredArgTypes;
int agg_nargs;
(void) get_func_signature(aggref->aggfnoid,
- &agg_arg_types, &agg_nargs);
- Assert(agg_nargs == 1);
- aggtranstype = resolve_generic_type(aggtranstype,
- inputType,
- agg_arg_types[0]);
- pfree(agg_arg_types);
+ &declaredArgTypes, &agg_nargs);
+ Assert(agg_nargs == numArguments);
+ aggtranstype = enforce_generic_type_consistency(inputTypes,
+ declaredArgTypes,
+ agg_nargs,
+ aggtranstype);
+ pfree(declaredArgTypes);
}
/* build expression trees using actual argument & result types */
- build_aggregate_fnexprs(inputType,
+ build_aggregate_fnexprs(inputTypes,
+ numArguments,
aggtranstype,
aggref->aggtype,
transfn_oid,
/*
* If the transfn is strict and the initval is NULL, make sure input
- * type and transtype are the same (or at least binary- compatible),
+ * 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. This should have been checked at agg definition time,
* but just in case...
*/
if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
{
- if (!IsBinaryCoercible(inputType, aggtranstype))
+ if (numArguments < 1 ||
+ !IsBinaryCoercible(inputTypes[0], aggtranstype))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("aggregate %u needs to have compatible input type and transition type",
/* We don't implement DISTINCT aggs in the HASHED case */
Assert(node->aggstrategy != AGG_HASHED);
- peraggstate->inputType = inputType;
- get_typlenbyval(inputType,
+ /*
+ * We don't currently implement DISTINCT aggs for aggs having
+ * more than one argument. This isn't required for anything
+ * in the SQL spec, but really it ought to be implemented for
+ * feature-completeness. FIXME someday.
+ */
+ if (numArguments != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DISTINCT is supported only for single-argument aggregates")));
+
+ peraggstate->inputType = inputTypes[0];
+ get_typlenbyval(inputTypes[0],
&peraggstate->inputtypeLen,
&peraggstate->inputtypeByVal);
- eq_function = equality_oper_funcid(inputType);
+ eq_function = equality_oper_funcid(inputTypes[0]);
fmgr_info(eq_function, &(peraggstate->equalfn));
- peraggstate->sortOperator = ordering_oper_opid(inputType);
+ peraggstate->sortOperator = ordering_oper_opid(inputTypes[0]);
peraggstate->sortstate = NULL;
}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.343 2006/07/14 14:52:19 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.344 2006/07/27 19:52:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
COPY_SCALAR_FIELD(aggfnoid);
COPY_SCALAR_FIELD(aggtype);
- COPY_NODE_FIELD(target);
+ COPY_NODE_FIELD(args);
COPY_SCALAR_FIELD(agglevelsup);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(aggdistinct);
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.277 2006/07/14 14:52:20 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.278 2006/07/27 19:52:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
{
COMPARE_SCALAR_FIELD(aggfnoid);
COMPARE_SCALAR_FIELD(aggtype);
- COMPARE_NODE_FIELD(target);
+ COMPARE_NODE_FIELD(args);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(aggdistinct);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.278 2006/07/14 14:52:20 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.279 2006/07/27 19:52:05 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
WRITE_OID_FIELD(aggfnoid);
WRITE_OID_FIELD(aggtype);
- WRITE_NODE_FIELD(target);
+ WRITE_NODE_FIELD(args);
WRITE_UINT_FIELD(agglevelsup);
WRITE_BOOL_FIELD(aggstar);
WRITE_BOOL_FIELD(aggdistinct);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.191 2006/07/03 22:45:39 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.192 2006/07/27 19:52:05 tgl Exp $
*
* NOTES
* Path and Plan nodes do not have any readfuncs support, because we
READ_OID_FIELD(aggfnoid);
READ_OID_FIELD(aggtype);
- READ_NODE_FIELD(target);
+ READ_NODE_FIELD(args);
READ_UINT_FIELD(agglevelsup);
READ_BOOL_FIELD(aggstar);
READ_BOOL_FIELD(aggdistinct);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.19 2006/07/26 19:31:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.20 2006/07/27 19:52:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
{
Aggref *aggref = (Aggref *) node;
Oid aggsortop;
+ Expr *curTarget;
MinMaxAggInfo *info;
ListCell *l;
Assert(aggref->agglevelsup == 0);
- if (aggref->aggstar)
- return true; /* foo(*) is surely not optimizable */
+ if (list_length(aggref->args) != 1)
+ return true; /* it couldn't be MIN/MAX */
/* note: we do not care if DISTINCT is mentioned ... */
aggsortop = fetch_agg_sort_op(aggref->aggfnoid);
/*
* Check whether it's already in the list, and add it if not.
*/
+ curTarget = linitial(aggref->args);
foreach(l, *context)
{
info = (MinMaxAggInfo *) lfirst(l);
if (info->aggfnoid == aggref->aggfnoid &&
- equal(info->target, aggref->target))
+ equal(info->target, curTarget))
return false;
}
info = (MinMaxAggInfo *) palloc0(sizeof(MinMaxAggInfo));
info->aggfnoid = aggref->aggfnoid;
info->aggsortop = aggsortop;
- info->target = aggref->target;
+ info->target = curTarget;
*context = lappend(*context, info);
{
Aggref *aggref = (Aggref *) node;
ListCell *l;
+ Expr *curTarget = linitial(aggref->args);
foreach(l, *context)
{
MinMaxAggInfo *info = (MinMaxAggInfo *) lfirst(l);
if (info->aggfnoid == aggref->aggfnoid &&
- equal(info->target, aggref->target))
+ equal(info->target, curTarget))
return (Node *) info->param;
}
elog(ERROR, "failed to re-find aggregate info record");
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.214 2006/07/14 14:52:21 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.215 2006/07/27 19:52:05 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
if (IsA(node, Aggref))
{
Aggref *aggref = (Aggref *) node;
- Oid inputType;
+ Oid *inputTypes;
+ int numArguments;
HeapTuple aggTuple;
Form_pg_aggregate aggform;
Oid aggtranstype;
+ int i;
+ ListCell *l;
Assert(aggref->agglevelsup == 0);
counts->numAggs++;
if (aggref->aggdistinct)
counts->numDistinctAggs++;
- inputType = exprType((Node *) aggref->target);
+ /* extract argument types */
+ numArguments = list_length(aggref->args);
+ inputTypes = (Oid *) palloc(sizeof(Oid) * numArguments);
+ i = 0;
+ foreach(l, aggref->args)
+ {
+ inputTypes[i++] = exprType((Node *) lfirst(l));
+ }
/* fetch aggregate transition datatype from pg_aggregate */
aggTuple = SearchSysCache(AGGFNOID,
/* resolve actual type of transition state, if polymorphic */
if (aggtranstype == ANYARRAYOID || aggtranstype == ANYELEMENTOID)
{
- /* have to fetch the agg's declared input type... */
- Oid *agg_arg_types;
+ /* have to fetch the agg's declared input types... */
+ Oid *declaredArgTypes;
int agg_nargs;
(void) get_func_signature(aggref->aggfnoid,
- &agg_arg_types, &agg_nargs);
- Assert(agg_nargs == 1);
- aggtranstype = resolve_generic_type(aggtranstype,
- inputType,
- agg_arg_types[0]);
- pfree(agg_arg_types);
+ &declaredArgTypes, &agg_nargs);
+ Assert(agg_nargs == numArguments);
+ aggtranstype = enforce_generic_type_consistency(inputTypes,
+ declaredArgTypes,
+ agg_nargs,
+ aggtranstype);
+ pfree(declaredArgTypes);
}
/*
int32 avgwidth;
/*
- * If transition state is of same type as 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 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 (aggtranstype == inputType)
- aggtranstypmod = exprTypmod((Node *) aggref->target);
+ if (numArguments > 0 && aggtranstype == inputTypes[0])
+ aggtranstypmod = exprTypmod((Node *) linitial(aggref->args));
else
aggtranstypmod = -1;
}
/*
- * Complain if the aggregate's argument contains any aggregates;
+ * Complain if the aggregate's arguments contain any aggregates;
* nested agg functions are semantically nonsensical.
*/
- if (contain_agg_clause((Node *) aggref->target))
+ if (contain_agg_clause((Node *) aggref->args))
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("aggregate function calls may not be nested")));
/* primitive node types with no expression subnodes */
break;
case T_Aggref:
- return walker(((Aggref *) node)->target, context);
+ {
+ Aggref *expr = (Aggref *) node;
+
+ if (expression_tree_walker((Node *) expr->args,
+ walker, context))
+ return true;
+ }
+ break;
case T_ArrayRef:
{
ArrayRef *aref = (ArrayRef *) node;
Aggref *newnode;
FLATCOPY(newnode, aggref, Aggref);
- MUTATE(newnode->target, aggref->target, Expr *);
+ MUTATE(newnode->args, aggref->args, List *);
return (Node *) newnode;
}
break;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.551 2006/07/03 22:45:39 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.552 2006/07/27 19:52:05 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
| func_name '(' '*' ')'
{
/*
- * For now, we transform AGGREGATE(*) into AGGREGATE(1).
- *
- * This does the right thing for COUNT(*) (in fact,
- * any certainly-non-null expression would do for COUNT),
+ * We consider AGGREGATE(*) to invoke a parameterless
+ * aggregate. This does the right thing for COUNT(*),
* and there are no other aggregates in SQL92 that accept
* '*' as parameter.
*
* really was.
*/
FuncCall *n = makeNode(FuncCall);
- A_Const *star = makeNode(A_Const);
-
- star->val.type = T_Integer;
- star->val.val.ival = 1;
n->funcname = $1;
- n->args = list_make1(star);
+ n->args = NIL;
n->agg_star = TRUE;
n->agg_distinct = FALSE;
n->location = @1;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.72 2006/07/14 14:52:21 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_agg.c,v 1.73 2006/07/27 19:52:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* The aggregate's level is the same as the level of the lowest-level
- * variable or aggregate in its argument; or if it contains no variables
+ * variable or aggregate in its arguments; or if it contains no variables
* at all, we presume it to be local.
*/
- min_varlevel = find_minimum_var_level((Node *) agg->target);
+ min_varlevel = find_minimum_var_level((Node *) agg->args);
/*
* An aggregate can't directly contain another aggregate call of the same
*/
if (min_varlevel == 0)
{
- if (checkExprHasAggs((Node *) agg->target))
+ if (checkExprHasAggs((Node *) agg->args))
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("aggregate function calls may not be nested")));
* (The trees will never actually be executed, however, so we can skimp
* a bit on correctness.)
*
- * agg_input_type, agg_state_type, agg_result_type identify the input,
+ * agg_input_types, agg_state_type, agg_result_type identify the input,
* transition, and result types of the aggregate. These should all be
* resolved to actual types (ie, none should ever be ANYARRAY or ANYELEMENT).
*
* *finalfnexpr. The latter is set to NULL if there's no finalfn.
*/
void
-build_aggregate_fnexprs(Oid agg_input_type,
+build_aggregate_fnexprs(Oid *agg_input_types,
+ int agg_num_inputs,
Oid agg_state_type,
Oid agg_result_type,
Oid transfn_oid,
Expr **transfnexpr,
Expr **finalfnexpr)
{
- int transfn_nargs;
- Param *arg0;
- Param *arg1;
+ Param *argp;
List *args;
-
- /* get the transition function arg count */
- transfn_nargs = get_func_nargs(transfn_oid);
+ int i;
/*
* Build arg list to use in the transfn FuncExpr node. We really only care
* get_fn_expr_argtype(), so it's okay to use Param nodes that don't
* correspond to any real Param.
*/
- arg0 = makeNode(Param);
- arg0->paramkind = PARAM_EXEC;
- arg0->paramid = -1;
- arg0->paramtype = agg_state_type;
+ argp = makeNode(Param);
+ argp->paramkind = PARAM_EXEC;
+ argp->paramid = -1;
+ argp->paramtype = agg_state_type;
- if (transfn_nargs == 2)
- {
- arg1 = makeNode(Param);
- arg1->paramkind = PARAM_EXEC;
- arg1->paramid = -1;
- arg1->paramtype = agg_input_type;
+ args = list_make1(argp);
- args = list_make2(arg0, arg1);
+ for (i = 0; i < agg_num_inputs; i++)
+ {
+ argp = makeNode(Param);
+ argp->paramkind = PARAM_EXEC;
+ argp->paramid = -1;
+ argp->paramtype = agg_input_types[i];
+ args = lappend(args, argp);
}
- else
- args = list_make1(arg0);
*transfnexpr = (Expr *) makeFuncExpr(transfn_oid,
agg_state_type,
/*
* Build expr tree for final function
*/
- arg0 = makeNode(Param);
- arg0->paramkind = PARAM_EXEC;
- arg0->paramid = -1;
- arg0->paramtype = agg_state_type;
- args = list_make1(arg0);
+ argp = makeNode(Param);
+ argp->paramkind = PARAM_EXEC;
+ argp->paramid = -1;
+ argp->paramtype = agg_state_type;
+ args = list_make1(argp);
*finalfnexpr = (Expr *) makeFuncExpr(finalfn_oid,
agg_result_type,
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.188 2006/07/14 14:52:22 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.189 2006/07/27 19:52:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
aggref->aggfnoid = funcid;
aggref->aggtype = rettype;
- aggref->target = linitial(fargs);
+ aggref->args = fargs;
aggref->aggstar = agg_star;
aggref->aggdistinct = agg_distinct;
+ /*
+ * Reject attempt to call a parameterless aggregate without (*)
+ * syntax. This is mere pedantry but some folks insisted ...
+ */
+ if (fargs == NIL && !agg_star)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s(*) must be used to call a parameterless aggregate function",
+ NameListToString(funcname)),
+ parser_errposition(pstate, location)));
+
/* parse_agg.c does additional aggregate-specific processing */
transformAggregateCall(pstate, aggref);
*
* This is almost like LookupFuncNameTypeNames, but the error messages refer
* to aggregates rather than plain functions, and we verify that the found
- * function really is an aggregate, and we recognize the convention used by
- * the grammar that agg(*) translates to a NIL list, which we have to treat
- * as one ANY argument. (XXX this ought to be changed)
+ * function really is an aggregate.
*/
Oid
LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError)
Oid argoids[FUNC_MAX_ARGS];
int argcount;
int i;
- ListCell *args_item;
+ ListCell *lc;
Oid oid;
HeapTuple ftup;
Form_pg_proc pform;
errmsg("functions cannot have more than %d arguments",
FUNC_MAX_ARGS)));
- if (argcount == 0)
- {
- /* special case for agg(*) */
- argoids[0] = ANYOID;
- argcount = 1;
- }
- else
+ i = 0;
+ foreach(lc, argtypes)
{
- args_item = list_head(argtypes);
- for (i = 0; i < argcount; i++)
- {
- TypeName *t = (TypeName *) lfirst(args_item);
-
- argoids[i] = LookupTypeName(NULL, t);
-
- if (!OidIsValid(argoids[i]))
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("type \"%s\" does not exist",
- TypeNameToString(t))));
+ TypeName *t = (TypeName *) lfirst(lc);
- args_item = lnext(args_item);
- }
+ argoids[i] = LookupTypeName(NULL, t);
+ if (!OidIsValid(argoids[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" does not exist",
+ TypeNameToString(t))));
+ i++;
}
oid = LookupFuncName(aggname, argcount, argoids, true);
{
if (noError)
return InvalidOid;
- if (argcount == 1 && argoids[0] == ANYOID)
+ if (argcount == 0)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("aggregate %s(*) does not exist",
* ruleutils.c - Functions to convert stored expressions/querytrees
* back to source text
*
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.228 2006/07/14 14:52:24 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.229 2006/07/27 19:52:06 tgl Exp $
**********************************************************************/
#include "postgres.h"
get_agg_expr(Aggref *aggref, deparse_context *context)
{
StringInfo buf = context->buf;
- Oid argtype = exprType((Node *) aggref->target);
+ Oid argtypes[FUNC_MAX_ARGS];
+ int nargs;
+ ListCell *l;
+
+ nargs = 0;
+ foreach(l, aggref->args)
+ {
+ if (nargs >= FUNC_MAX_ARGS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg("too many arguments")));
+ argtypes[nargs] = exprType((Node *) lfirst(l));
+ nargs++;
+ }
appendStringInfo(buf, "%s(%s",
- generate_function_name(aggref->aggfnoid, 1, &argtype),
+ generate_function_name(aggref->aggfnoid, nargs, argtypes),
aggref->aggdistinct ? "DISTINCT " : "");
+ /* aggstar can be set only in zero-argument aggregates */
if (aggref->aggstar)
- appendStringInfo(buf, "*");
+ appendStringInfoChar(buf, '*');
else
- get_rule_expr((Node *) aggref->target, context, true);
+ get_rule_expr((Node *) aggref->args, context, true);
appendStringInfoChar(buf, ')');
}
* by PostgreSQL
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.441 2006/07/14 14:52:26 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.442 2006/07/27 19:52:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
int i_oid;
int i_aggname;
int i_aggnamespace;
- int i_aggbasetype;
+ int i_pronargs;
+ int i_proargtypes;
int i_rolname;
int i_aggacl;
/* find all user-defined aggregates */
- if (g_fout->remoteVersion >= 70300)
+ if (g_fout->remoteVersion >= 80200)
{
appendPQExpBuffer(query, "SELECT tableoid, oid, proname as aggname, "
"pronamespace as aggnamespace, "
- "proargtypes[0] as aggbasetype, "
+ "pronargs, proargtypes, "
+ "(%s proowner) as rolname, "
+ "proacl as aggacl "
+ "FROM pg_proc "
+ "WHERE proisagg "
+ "AND pronamespace != "
+ "(select oid from pg_namespace where nspname = 'pg_catalog')",
+ username_subquery);
+ }
+ else if (g_fout->remoteVersion >= 70300)
+ {
+ appendPQExpBuffer(query, "SELECT tableoid, oid, proname as aggname, "
+ "pronamespace as aggnamespace, "
+ "CASE WHEN proargtypes[0] = 'pg_catalog.\"any\"'::pg_catalog.regtype THEN 0 ELSE 1 END as pronargs, "
+ "proargtypes, "
"(%s proowner) as rolname, "
"proacl as aggacl "
"FROM pg_proc "
{
appendPQExpBuffer(query, "SELECT tableoid, oid, aggname, "
"0::oid as aggnamespace, "
- "aggbasetype, "
+ "CASE WHEN aggbasetype = 0 THEN 0 ELSE 1 END as pronargs, "
+ "aggbasetype as proargtypes, "
"(%s aggowner) as rolname, "
"'{=X}' as aggacl "
"FROM pg_aggregate "
"(SELECT oid FROM pg_class WHERE relname = 'pg_aggregate') AS tableoid, "
"oid, aggname, "
"0::oid as aggnamespace, "
- "aggbasetype, "
+ "CASE WHEN aggbasetype = 0 THEN 0 ELSE 1 END as pronargs, "
+ "aggbasetype as proargtypes, "
"(%s aggowner) as rolname, "
"'{=X}' as aggacl "
"FROM pg_aggregate "
i_oid = PQfnumber(res, "oid");
i_aggname = PQfnumber(res, "aggname");
i_aggnamespace = PQfnumber(res, "aggnamespace");
- i_aggbasetype = PQfnumber(res, "aggbasetype");
+ i_pronargs = PQfnumber(res, "pronargs");
+ i_proargtypes = PQfnumber(res, "proargtypes");
i_rolname = PQfnumber(res, "rolname");
i_aggacl = PQfnumber(res, "aggacl");
write_msg(NULL, "WARNING: owner of aggregate function \"%s\" appears to be invalid\n",
agginfo[i].aggfn.dobj.name);
agginfo[i].aggfn.lang = InvalidOid; /* not currently interesting */
- agginfo[i].aggfn.nargs = 1;
- agginfo[i].aggfn.argtypes = (Oid *) malloc(sizeof(Oid));
- agginfo[i].aggfn.argtypes[0] = atooid(PQgetvalue(res, i, i_aggbasetype));
agginfo[i].aggfn.prorettype = InvalidOid; /* not saved */
agginfo[i].aggfn.proacl = strdup(PQgetvalue(res, i, i_aggacl));
- agginfo[i].anybasetype = false; /* computed when it's dumped */
- agginfo[i].fmtbasetype = NULL; /* computed when it's dumped */
+ agginfo[i].aggfn.nargs = atoi(PQgetvalue(res, i, i_pronargs));
+ if (agginfo[i].aggfn.nargs == 0)
+ agginfo[i].aggfn.argtypes = NULL;
+ else
+ {
+ agginfo[i].aggfn.argtypes = (Oid *) malloc(agginfo[i].aggfn.nargs * sizeof(Oid));
+ if (g_fout->remoteVersion >= 70300)
+ parseOidArray(PQgetvalue(res, i, i_proargtypes),
+ agginfo[i].aggfn.argtypes,
+ agginfo[i].aggfn.nargs);
+ else /* it's just aggbasetype */
+ agginfo[i].aggfn.argtypes[0] = atooid(PQgetvalue(res, i, i_proargtypes));
+ }
/* Decide whether we want to dump it */
selectDumpableObject(&(agginfo[i].aggfn.dobj));
format_aggregate_signature(AggInfo *agginfo, Archive *fout, bool honor_quotes)
{
PQExpBufferData buf;
+ int j;
initPQExpBuffer(&buf);
if (honor_quotes)
else
appendPQExpBuffer(&buf, "%s", agginfo->aggfn.dobj.name);
- /* If using regtype or format_type, fmtbasetype is already quoted */
- if (fout->remoteVersion >= 70100)
- {
- if (agginfo->anybasetype)
- appendPQExpBuffer(&buf, "(*)");
- else
- appendPQExpBuffer(&buf, "(%s)", agginfo->fmtbasetype);
- }
+ if (agginfo->aggfn.nargs == 0)
+ appendPQExpBuffer(&buf, "(*)");
else
{
- if (agginfo->anybasetype)
- appendPQExpBuffer(&buf, "(*)");
- else
- appendPQExpBuffer(&buf, "(%s)",
- fmtId(agginfo->fmtbasetype));
- }
+ appendPQExpBuffer(&buf, "(");
+ for (j = 0; j < agginfo->aggfn.nargs; j++)
+ {
+ char *typname;
+
+ typname = getFormattedTypeName(agginfo->aggfn.argtypes[j], zeroAsOpaque);
+ appendPQExpBuffer(&buf, "%s%s",
+ (j > 0) ? ", " : "",
+ typname);
+ free(typname);
+ }
+ appendPQExpBuffer(&buf, ")");
+ }
return buf.data;
}
int i_aggsortop;
int i_aggtranstype;
int i_agginitval;
- int i_anybasetype;
- int i_fmtbasetype;
int i_convertok;
const char *aggtransfn;
const char *aggfinalfn;
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"aggsortop::pg_catalog.regoperator, "
"agginitval, "
- "proargtypes[0] = 'pg_catalog.\"any\"'::pg_catalog.regtype as anybasetype, "
- "proargtypes[0]::pg_catalog.regtype as fmtbasetype, "
"'t'::boolean as convertok "
"from pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
"where a.aggfnoid = p.oid "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
"0 as aggsortop, "
"agginitval, "
- "proargtypes[0] = 'pg_catalog.\"any\"'::pg_catalog.regtype as anybasetype, "
- "proargtypes[0]::pg_catalog.regtype as fmtbasetype, "
"'t'::boolean as convertok "
"from pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
"where a.aggfnoid = p.oid "
"format_type(aggtranstype, NULL) as aggtranstype, "
"0 as aggsortop, "
"agginitval, "
- "aggbasetype = 0 as anybasetype, "
- "CASE WHEN aggbasetype = 0 THEN '-' "
- "ELSE format_type(aggbasetype, NULL) END as fmtbasetype, "
"'t'::boolean as convertok "
"from pg_aggregate "
"where oid = '%u'::oid",
"(select typname from pg_type where oid = aggtranstype1) as aggtranstype, "
"0 as aggsortop, "
"agginitval1 as agginitval, "
- "aggbasetype = 0 as anybasetype, "
- "(select typname from pg_type where oid = aggbasetype) as fmtbasetype, "
"(aggtransfn2 = 0 and aggtranstype2 = 0 and agginitval2 is null) as convertok "
"from pg_aggregate "
"where oid = '%u'::oid",
i_aggsortop = PQfnumber(res, "aggsortop");
i_aggtranstype = PQfnumber(res, "aggtranstype");
i_agginitval = PQfnumber(res, "agginitval");
- i_anybasetype = PQfnumber(res, "anybasetype");
- i_fmtbasetype = PQfnumber(res, "fmtbasetype");
i_convertok = PQfnumber(res, "convertok");
aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
aggsortop = PQgetvalue(res, 0, i_aggsortop);
aggtranstype = PQgetvalue(res, 0, i_aggtranstype);
agginitval = PQgetvalue(res, 0, i_agginitval);
- /* we save anybasetype for format_aggregate_signature */
- agginfo->anybasetype = (PQgetvalue(res, 0, i_anybasetype)[0] == 't');
- /* we save fmtbasetype for format_aggregate_signature */
- agginfo->fmtbasetype = strdup(PQgetvalue(res, 0, i_fmtbasetype));
convertok = (PQgetvalue(res, 0, i_convertok)[0] == 't');
aggsig = format_aggregate_signature(agginfo, fout, true);
if (g_fout->remoteVersion >= 70300)
{
/* If using 7.3's regproc or regtype, data is already quoted */
- appendPQExpBuffer(details, " BASETYPE = %s,\n SFUNC = %s,\n STYPE = %s",
- agginfo->anybasetype ? "'any'" :
- agginfo->fmtbasetype,
+ appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
aggtransfn,
aggtranstype);
}
else if (g_fout->remoteVersion >= 70100)
{
/* format_type quotes, regproc does not */
- appendPQExpBuffer(details, " BASETYPE = %s,\n SFUNC = %s,\n STYPE = %s",
- agginfo->anybasetype ? "'any'" :
- agginfo->fmtbasetype,
+ appendPQExpBuffer(details, " SFUNC = %s,\n STYPE = %s",
fmtId(aggtransfn),
aggtranstype);
}
else
{
/* need quotes all around */
- appendPQExpBuffer(details, " BASETYPE = %s,\n",
- agginfo->anybasetype ? "'any'" :
- fmtId(agginfo->fmtbasetype));
appendPQExpBuffer(details, " SFUNC = %s,\n",
fmtId(aggtransfn));
appendPQExpBuffer(details, " STYPE = %s",
aggsig);
appendPQExpBuffer(q, "CREATE AGGREGATE %s (\n%s\n);\n",
- fmtId(agginfo->aggfn.dobj.name),
- details->data);
+ aggsig, details->data);
ArchiveEntry(fout, agginfo->aggfn.dobj.catId, agginfo->aggfn.dobj.dumpId,
aggsig_tag,
/*
* 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 aggregates on ANY.
+ * syntax for zero-argument aggregates.
*/
free(aggsig);
free(aggsig_tag);
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.126 2006/07/02 02:23:21 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.127 2006/07/27 19:52:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
typedef struct _aggInfo
{
FuncInfo aggfn;
- bool anybasetype; /* is the basetype "any"? */
- char *fmtbasetype; /* formatted type name */
+ /* we don't require any other fields at the moment */
} AggInfo;
typedef struct _oprInfo
*
* Copyright (c) 2000-2006, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.141 2006/07/17 00:21:23 neilc Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.142 2006/07/27 19:52:06 tgl Exp $
*/
#include "postgres_fe.h"
#include "describe.h"
printfPQExpBuffer(&buf,
"SELECT n.nspname as \"%s\",\n"
" p.proname AS \"%s\",\n"
- " CASE p.proargtypes[0]\n"
- " WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype\n"
- " THEN CAST('%s' AS pg_catalog.text)\n"
- " ELSE pg_catalog.format_type(p.proargtypes[0], NULL)\n"
+ " CASE WHEN p.pronargs = 0\n"
+ " THEN CAST('*' AS pg_catalog.text)\n"
+ " ELSE\n"
+ " pg_catalog.array_to_string(ARRAY(\n"
+ " SELECT\n"
+ " pg_catalog.format_type(p.proargtypes[s.i], NULL)\n"
+ " FROM\n"
+ " pg_catalog.generate_series(0, pg_catalog.array_upper(p.proargtypes, 1)) AS s(i)\n"
+ " ), ', ')\n"
" END AS \"%s\",\n"
" pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n"
"FROM pg_catalog.pg_proc p\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n"
"WHERE p.proisagg\n",
- _("Schema"), _("Name"), _("(all types)"),
- _("Data type"), _("Description"));
+ _("Schema"), _("Name"),
+ _("Argument data types"), _("Description"));
processNamePattern(&buf, pattern, true, false,
"n.nspname", "p.proname", NULL,
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.341 2006/07/26 19:31:51 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.342 2006/07/27 19:52:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200607261
+#define CATALOG_VERSION_NO 200607271
#endif
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_aggregate.h,v 1.55 2006/07/21 20:51:33 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_aggregate.h,v 1.56 2006/07/27 19:52:06 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
DATA(insert ( 2245 bpchar_smaller - 1058 1042 _null_ ));
DATA(insert ( 2798 tidsmaller - 2799 27 _null_ ));
-/*
- * Using int8inc for count() is cheating a little, since it really only
- * takes 1 parameter not 2, but nodeAgg.c won't complain ...
- */
-DATA(insert ( 2147 int8inc - 0 20 0 ));
+/* count */
+DATA(insert ( 2147 int8inc_any - 0 20 "0" ));
+DATA(insert ( 2803 int8inc - 0 20 "0" ));
/* var_pop */
DATA(insert ( 2718 int8_accum numeric_var_pop 0 1231 "{0,0,0}" ));
*/
extern void AggregateCreate(const char *aggName,
Oid aggNamespace,
- Oid aggBaseType,
+ Oid *aggArgTypes,
+ int numArgs,
List *aggtransfnName,
List *aggfinalfnName,
List *aggsortopName,
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.417 2006/07/25 03:51:21 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.418 2006/07/27 19:52:06 tgl Exp $
*
* NOTES
* The script catalog/genbki.sh reads this file and generates .bki
DATA(insert OID = 1219 ( int8inc PGNSP PGUID 12 f f t f i 1 20 "20" _null_ _null_ _null_ int8inc - _null_ ));
DESCR("increment");
+DATA(insert OID = 2804 ( int8inc_any PGNSP PGUID 12 f f t f i 2 20 "20 2276" _null_ _null_ _null_ int8inc - _null_ ));
+DESCR("increment, ignores second argument");
DATA(insert OID = 1230 ( int8abs PGNSP PGUID 12 f f t f i 1 20 "20" _null_ _null_ _null_ int8abs - _null_ ));
DESCR("absolute value");
DATA(insert OID = 2245 ( min PGNSP PGUID 12 t f f f i 1 1042 "1042" _null_ _null_ _null_ aggregate_dummy - _null_ ));
DATA(insert OID = 2798 ( min PGNSP PGUID 12 t f f f i 1 27 "27" _null_ _null_ _null_ aggregate_dummy - _null_ ));
+/* count has two forms: count(any) and count(*) */
DATA(insert OID = 2147 ( count PGNSP PGUID 12 t f f f i 1 20 "2276" _null_ _null_ _null_ aggregate_dummy - _null_ ));
+DATA(insert OID = 2803 ( count PGNSP PGUID 12 t f f f i 0 20 "" _null_ _null_ _null_ aggregate_dummy - _null_ ));
DATA(insert OID = 2718 ( var_pop PGNSP PGUID 12 t f f f i 1 1700 "20" _null_ _null_ _null_ aggregate_dummy - _null_ ));
DATA(insert OID = 2719 ( var_pop PGNSP PGUID 12 t f f f i 1 1700 "23" _null_ _null_ _null_ aggregate_dummy - _null_ ));
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.154 2006/07/26 00:34:48 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.155 2006/07/27 19:52:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
typedef struct AggrefExprState
{
ExprState xprstate;
- ExprState *target; /* state of my child node */
+ List *args; /* states of argument expressions */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.114 2006/07/13 16:49:19 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.115 2006/07/27 19:52:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Expr xpr;
Oid aggfnoid; /* pg_proc Oid of the aggregate */
Oid aggtype; /* type Oid of result of the aggregate */
- Expr *target; /* expression we are aggregating on */
+ List *args; /* arguments to the aggregate */
Index agglevelsup; /* > 0 if agg belongs to outer query */
- bool aggstar; /* TRUE if argument was really '*' */
+ bool aggstar; /* TRUE if argument list was really '*' */
bool aggdistinct; /* TRUE if it's agg(DISTINCT ...) */
} Aggref;
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.33 2006/03/05 15:58:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_agg.h,v 1.34 2006/07/27 19:52:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern void parseCheckAggregates(ParseState *pstate, Query *qry);
-extern void build_aggregate_fnexprs(Oid agg_input_type,
+extern void build_aggregate_fnexprs(Oid *agg_input_types,
+ int agg_num_inputs,
Oid agg_state_type,
Oid agg_result_type,
Oid transfn_oid,
9 | 100 | 4
(10 rows)
+-- user-defined aggregates
SELECT newavg(four) AS avg_1 FROM onek;
avg_1
--------------------
1000
(1 row)
+SELECT newcnt(*) AS cnt_1000 FROM onek;
+ cnt_1000
+----------
+ 1000
+(1 row)
+
+SELECT oldcnt(*) AS cnt_1000 FROM onek;
+ cnt_1000
+----------
+ 1000
+(1 row)
+
+SELECT sum2(q1,q2) FROM int8_tbl;
+ sum2
+-------------------
+ 18271560493827981
+(1 row)
+
-- test for outer-level aggregates
-- this should work
select ten, sum(distinct four) from onek a
sfunc1 = int4pl, basetype = int4, stype1 = int4,
initcond1 = '0'
);
--- value-independent transition function
-CREATE AGGREGATE newcnt (
- sfunc = int4inc, basetype = 'any', stype = int4,
+-- zero-argument aggregate
+CREATE AGGREGATE newcnt (*) (
+ sfunc = int8inc, stype = int8,
+ initcond = '0'
+);
+-- old-style spelling of same
+CREATE AGGREGATE oldcnt (
+ sfunc = int8inc, basetype = 'ANY', stype = int8,
+ initcond = '0'
+);
+-- aggregate that only cares about null/nonnull input
+CREATE AGGREGATE newcnt ("any") (
+ sfunc = int8inc_any, stype = int8,
+ initcond = '0'
+);
+-- multi-argument aggregate
+create function sum3(int8,int8,int8) returns int8 as
+'select $1 + $2 + $3' language sql strict immutable;
+create aggregate sum2(int8,int8) (
+ sfunc = sum3, stype = int8,
initcond = '0'
);
COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
ERROR: aggregate nosuchagg(*) does not exist
-COMMENT ON AGGREGATE newcnt (*) IS 'an any agg comment';
-COMMENT ON AGGREGATE newcnt (*) IS NULL;
+COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment';
+COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment';
-- Look for conflicting proc definitions (same names and input datatypes).
-- (This test should be dead code now that we have the unique index
--- pg_proc_proname_narg_type_index, but I'll leave it in anyway.)
+-- pg_proc_proname_args_nsp_index, but I'll leave it in anyway.)
SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2
WHERE p1.oid != p2.oid AND
-- have several entries with different pronames for the same internal function,
-- but conflicts in the number of arguments and other critical items should
-- be complained of.
+-- Ignore aggregates, since they all use "aggregate_dummy".
+-- As of 8.2, this finds int8inc and int8inc_any, which are OK.
SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2
-WHERE p1.oid != p2.oid AND
+WHERE p1.oid < p2.oid AND
p1.prosrc = p2.prosrc AND
p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.proisagg = false AND p2.proisagg = false AND
(p1.prolang != p2.prolang OR
p1.proisagg != p2.proisagg OR
p1.prosecdef != p2.prosecdef OR
p1.proretset != p2.proretset OR
p1.provolatile != p2.provolatile OR
p1.pronargs != p2.pronargs);
- oid | proname | oid | proname
------+---------+-----+---------
-(0 rows)
+ oid | proname | oid | proname
+------+---------+------+-------------
+ 1219 | int8inc | 2804 | int8inc_any
+(1 row)
-- Look for uses of different type OIDs in the argument/result type fields
-- for different aliases of the same built-in function.
SELECT a.aggfnoid::oid, p.proname
FROM pg_aggregate as a, pg_proc as p
WHERE a.aggfnoid = p.oid AND
- (NOT p.proisagg OR p.pronargs != 1 OR p.proretset);
+ (NOT p.proisagg OR p.proretset);
aggfnoid | proname
----------+---------
(0 rows)
WHERE a.aggfnoid = p.oid AND
a.aggtransfn = ptr.oid AND
(ptr.proretset
+ OR NOT (ptr.pronargs = p.pronargs + 1)
OR NOT physically_coercible(ptr.prorettype, a.aggtranstype)
OR NOT physically_coercible(a.aggtranstype, ptr.proargtypes[0])
- OR NOT ((ptr.pronargs = 2 AND
- physically_coercible(p.proargtypes[0], ptr.proargtypes[1]))
- OR
- (ptr.pronargs = 1 AND
- p.proargtypes[0] = '"any"'::regtype)));
+ OR (p.pronargs > 0 AND
+ NOT physically_coercible(p.proargtypes[0], ptr.proargtypes[1]))
+ OR (p.pronargs > 1 AND
+ NOT physically_coercible(p.proargtypes[1], ptr.proargtypes[2]))
+ OR (p.pronargs > 2 AND
+ NOT physically_coercible(p.proargtypes[2], ptr.proargtypes[3]))
+ -- we could carry the check further, but that's enough for now
+ );
aggfnoid | proname | oid | proname
----------+---------+-----+---------
(0 rows)
-- arg2 only polymorphic transfn
CREATE FUNCTION tf2p(int[],anyelement) RETURNS int[] AS
'select $1' LANGUAGE SQL;
+-- multi-arg polymorphic
+CREATE FUNCTION sum3(anyelement,anyelement,anyelement) returns anyelement AS
+'select $1+$2+$3' language sql strict;
-- finalfn polymorphic
CREATE FUNCTION ffp(anyarray) RETURNS anyarray AS
'select $1' LANGUAGE SQL;
-- -------
-- N N
-- should CREATE
-CREATE AGGREGATE myaggp01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[],
+CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
FINALFUNC = ffp, INITCOND = '{}');
-- P N
-- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
-CREATE AGGREGATE myaggp02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray,
+CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
FINALFUNC = ffp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- N P
-- should CREATE
-CREATE AGGREGATE myaggp03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[],
+CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
FINALFUNC = ffp, INITCOND = '{}');
-CREATE AGGREGATE myaggp03b(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[],
+CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
INITCOND = '{}');
-- P P
-- should ERROR: we have no way to resolve S
-CREATE AGGREGATE myaggp04a(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray,
+CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
FINALFUNC = ffp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
-CREATE AGGREGATE myaggp04b(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray,
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
+CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- Case2 (R = P) && ((B = P) || (B = N))
-- -------------------------------------
-- S tf1 B tf2
CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
FINALFUNC = ffp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- P N N P
-- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
FINALFUNC = ffp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- P N P N
-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
FINALFUNC = ffp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- P P N P
-- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
FINALFUNC = ffp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- P P P N
-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
-- -------
-- N N
-- should CREATE
-CREATE AGGREGATE myaggn01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[],
+CREATE AGGREGATE myaggn01a(*) (SFUNC = stfnp, STYPE = int4[],
FINALFUNC = ffnp, INITCOND = '{}');
-CREATE AGGREGATE myaggn01b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[],
+CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
INITCOND = '{}');
-- P N
-- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
-CREATE AGGREGATE myaggn02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray,
+CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
FINALFUNC = ffnp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
-CREATE AGGREGATE myaggn02b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray,
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
+CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- N P
-- should CREATE
-CREATE AGGREGATE myaggn03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[],
+CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
FINALFUNC = ffnp, INITCOND = '{}');
-- P P
-- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
-CREATE AGGREGATE myaggn04a(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray,
+CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
FINALFUNC = ffnp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- Case4 (R = N) && ((B = P) || (B = N))
-- -------------------------------------
-- S tf1 B tf2
CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
FINALFUNC = ffnp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- P N N P
-- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
FINALFUNC = ffnp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- P N P N
-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
FINALFUNC = ffnp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- P P N P
-- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
FINALFUNC = ffnp, INITCOND = '{}');
ERROR: cannot determine transition data type
-DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have one of them as its base type.
+DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type.
-- P P P N
-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
CREATE AGGREGATE myaggn20a(BASETYPE = anyelement, SFUNC = tfp,
STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
ERROR: function ffnp(anyarray) does not exist
+-- multi-arg polymorphic
+CREATE AGGREGATE mysum2(anyelement,anyelement) (SFUNC = sum3,
+ STYPE = anyelement, INITCOND = '0');
-- create test data for polymorphic aggregates
create temp table t(f1 int, f2 int[], f3 text);
insert into t values(1,array[1],'a');
a | {1,2,3}
(3 rows)
+select mysum2(f1, f1 + 1) from t;
+ mysum2
+--------
+ 38
+(1 row)
+
select ten, count(four), sum(DISTINCT four) from onek
group by ten order by ten;
-
+-- user-defined aggregates
SELECT newavg(four) AS avg_1 FROM onek;
SELECT newsum(four) AS sum_1500 FROM onek;
SELECT newcnt(four) AS cnt_1000 FROM onek;
-
+SELECT newcnt(*) AS cnt_1000 FROM onek;
+SELECT oldcnt(*) AS cnt_1000 FROM onek;
+SELECT sum2(q1,q2) FROM int8_tbl;
-- test for outer-level aggregates
initcond1 = '0'
);
--- value-independent transition function
-CREATE AGGREGATE newcnt (
- sfunc = int4inc, basetype = 'any', stype = int4,
+-- zero-argument aggregate
+CREATE AGGREGATE newcnt (*) (
+ sfunc = int8inc, stype = int8,
+ initcond = '0'
+);
+
+-- old-style spelling of same
+CREATE AGGREGATE oldcnt (
+ sfunc = int8inc, basetype = 'ANY', stype = int8,
+ initcond = '0'
+);
+
+-- aggregate that only cares about null/nonnull input
+CREATE AGGREGATE newcnt ("any") (
+ sfunc = int8inc_any, stype = int8,
+ initcond = '0'
+);
+
+-- multi-argument aggregate
+create function sum3(int8,int8,int8) returns int8 as
+'select $1 + $2 + $3' language sql strict immutable;
+
+create aggregate sum2(int8,int8) (
+ sfunc = sum3, stype = int8,
initcond = '0'
);
COMMENT ON AGGREGATE nosuchagg (*) IS 'should fail';
-COMMENT ON AGGREGATE newcnt (*) IS 'an any agg comment';
-COMMENT ON AGGREGATE newcnt (*) IS NULL;
+COMMENT ON AGGREGATE newcnt (*) IS 'an agg(*) comment';
+COMMENT ON AGGREGATE newcnt ("any") IS 'an agg(any) comment';
-- Look for conflicting proc definitions (same names and input datatypes).
-- (This test should be dead code now that we have the unique index
--- pg_proc_proname_narg_type_index, but I'll leave it in anyway.)
+-- pg_proc_proname_args_nsp_index, but I'll leave it in anyway.)
SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2
-- have several entries with different pronames for the same internal function,
-- but conflicts in the number of arguments and other critical items should
-- be complained of.
+-- Ignore aggregates, since they all use "aggregate_dummy".
+
+-- As of 8.2, this finds int8inc and int8inc_any, which are OK.
SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2
-WHERE p1.oid != p2.oid AND
+WHERE p1.oid < p2.oid AND
p1.prosrc = p2.prosrc AND
p1.prolang = 12 AND p2.prolang = 12 AND
+ p1.proisagg = false AND p2.proisagg = false AND
(p1.prolang != p2.prolang OR
p1.proisagg != p2.proisagg OR
p1.prosecdef != p2.prosecdef OR
SELECT a.aggfnoid::oid, p.proname
FROM pg_aggregate as a, pg_proc as p
WHERE a.aggfnoid = p.oid AND
- (NOT p.proisagg OR p.pronargs != 1 OR p.proretset);
+ (NOT p.proisagg OR p.proretset);
-- Make sure there are no proisagg pg_proc entries without matches.
WHERE a.aggfnoid = p.oid AND
a.aggtransfn = ptr.oid AND
(ptr.proretset
+ OR NOT (ptr.pronargs = p.pronargs + 1)
OR NOT physically_coercible(ptr.prorettype, a.aggtranstype)
OR NOT physically_coercible(a.aggtranstype, ptr.proargtypes[0])
- OR NOT ((ptr.pronargs = 2 AND
- physically_coercible(p.proargtypes[0], ptr.proargtypes[1]))
- OR
- (ptr.pronargs = 1 AND
- p.proargtypes[0] = '"any"'::regtype)));
+ OR (p.pronargs > 0 AND
+ NOT physically_coercible(p.proargtypes[0], ptr.proargtypes[1]))
+ OR (p.pronargs > 1 AND
+ NOT physically_coercible(p.proargtypes[1], ptr.proargtypes[2]))
+ OR (p.pronargs > 2 AND
+ NOT physically_coercible(p.proargtypes[2], ptr.proargtypes[3]))
+ -- we could carry the check further, but that's enough for now
+ );
-- Cross-check finalfn (if present) against its entry in pg_proc.
CREATE FUNCTION tf2p(int[],anyelement) RETURNS int[] AS
'select $1' LANGUAGE SQL;
+-- multi-arg polymorphic
+CREATE FUNCTION sum3(anyelement,anyelement,anyelement) returns anyelement AS
+'select $1+$2+$3' language sql strict;
+
-- finalfn polymorphic
CREATE FUNCTION ffp(anyarray) RETURNS anyarray AS
'select $1' LANGUAGE SQL;
-- -------
-- N N
-- should CREATE
-CREATE AGGREGATE myaggp01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[],
+CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
FINALFUNC = ffp, INITCOND = '{}');
-- P N
-- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
-CREATE AGGREGATE myaggp02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray,
+CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
FINALFUNC = ffp, INITCOND = '{}');
-- N P
-- should CREATE
-CREATE AGGREGATE myaggp03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[],
+CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
FINALFUNC = ffp, INITCOND = '{}');
-CREATE AGGREGATE myaggp03b(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[],
+CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
INITCOND = '{}');
-- P P
-- should ERROR: we have no way to resolve S
-CREATE AGGREGATE myaggp04a(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray,
+CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
FINALFUNC = ffp, INITCOND = '{}');
-CREATE AGGREGATE myaggp04b(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray,
+CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
INITCOND = '{}');
-- -------
-- N N
-- should CREATE
-CREATE AGGREGATE myaggn01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[],
+CREATE AGGREGATE myaggn01a(*) (SFUNC = stfnp, STYPE = int4[],
FINALFUNC = ffnp, INITCOND = '{}');
-CREATE AGGREGATE myaggn01b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[],
+CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
INITCOND = '{}');
-- P N
-- should ERROR: stfnp(anyarray) not matched by stfnp(int[])
-CREATE AGGREGATE myaggn02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray,
+CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
FINALFUNC = ffnp, INITCOND = '{}');
-CREATE AGGREGATE myaggn02b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray,
+CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
INITCOND = '{}');
-- N P
-- should CREATE
-CREATE AGGREGATE myaggn03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[],
+CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
FINALFUNC = ffnp, INITCOND = '{}');
-- P P
-- should ERROR: ffnp(anyarray) not matched by ffnp(int[])
-CREATE AGGREGATE myaggn04a(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray,
+CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
FINALFUNC = ffnp, INITCOND = '{}');
CREATE AGGREGATE myaggn20a(BASETYPE = anyelement, SFUNC = tfp,
STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}');
+-- multi-arg polymorphic
+CREATE AGGREGATE mysum2(anyelement,anyelement) (SFUNC = sum3,
+ STYPE = anyelement, INITCOND = '0');
+
-- create test data for polymorphic aggregates
create temp table t(f1 int, f2 int[], f3 text);
insert into t values(1,array[1],'a');
select f3, myaggn08b(f1) from t group by f3;
select f3, myaggn09a(f1) from t group by f3;
select f3, myaggn10a(f1) from t group by f3;
+select mysum2(f1, f1 + 1) from t;