diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2006-07-27 19:52:07 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2006-07-27 19:52:07 +0000 |
commit | 108fe4730152058f9b576969d08898b39bf7fc38 (patch) | |
tree | 15a7d14be8267612cdfed4de8af86993b37f9997 /src | |
parent | c2d1138351f89d0705f71cf935a56b9a2e28ed24 (diff) | |
download | postgresql-108fe4730152058f9b576969d08898b39bf7fc38.tar.gz postgresql-108fe4730152058f9b576969d08898b39bf7fc38.zip |
Aggregate functions now support multiple input arguments. I also took
the opportunity to treat COUNT(*) as a zero-argument aggregate instead
of the old hack that equated it to COUNT(1); this is materially cleaner
(no more weird ANYOID cases) and ought to be at least a tiny bit faster.
Original patch by Sergey Koposov; review, documentation, simple regression
tests, pg_dump and psql support by moi.
Diffstat (limited to 'src')
31 files changed, 606 insertions, 400 deletions
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 47d21b35b01..747543e077d 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -42,7 +42,8 @@ static Oid lookup_agg_function(List *fnName, int nargs, Oid *input_types, void AggregateCreate(const char *aggName, Oid aggNamespace, - Oid aggBaseType, + Oid *aggArgTypes, + int numArgs, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, @@ -57,9 +58,10 @@ AggregateCreate(const char *aggName, 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; @@ -74,27 +76,34 @@ AggregateCreate(const char *aggName, 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); @@ -123,13 +132,14 @@ AggregateCreate(const char *aggName, 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"))); @@ -153,32 +163,37 @@ AggregateCreate(const char *aggName, 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, @@ -195,7 +210,8 @@ AggregateCreate(const char *aggName, 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 */ @@ -279,6 +295,8 @@ lookup_agg_function(List *fnName, Oid *true_oid_array; FuncDetailCode fdresult; AclResult aclresult; + int i; + bool allPolyArgs = true; /* * func_get_detail looks up the function in the catalogs, does @@ -307,13 +325,17 @@ lookup_agg_function(List *fnName, * 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, @@ -325,22 +347,16 @@ lookup_agg_function(List *fnName, * 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); diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c index 40b4d59cb9c..cb4dfee77e2 100644 --- a/src/backend/commands/aggregatecmds.c +++ b/src/backend/commands/aggregatecmds.c @@ -9,7 +9,7 @@ * * * 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 @@ -57,7 +57,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) TypeName *baseType = NULL; TypeName *transType = NULL; char *initval = NULL; - Oid baseTypeId; + Oid *aggArgTypes; + int numArgs; Oid transTypeId; ListCell *pl; @@ -116,12 +117,13 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) 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. @@ -132,37 +134,37 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) 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); } } @@ -187,7 +189,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) */ 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 */ @@ -211,7 +214,7 @@ RemoveAggregate(RemoveFuncStmt *stmt) /* 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 */ diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index fc80472adee..7f341940d67 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -3174,10 +3174,11 @@ ExecInitExpr(Expr *node, PlanState *parent) 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.) diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 851a360d2f1..19410997b2c 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -6,8 +6,8 @@ * 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 @@ -16,12 +16,12 @@ * 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 @@ -61,7 +61,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -103,6 +103,9 @@ typedef struct AggStatePerAggData 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 */ @@ -214,7 +217,7 @@ static void initialize_aggregates(AggState *aggstate, 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, @@ -314,7 +317,11 @@ initialize_aggregates(AggState *aggstate, } /* - * 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. */ @@ -322,19 +329,24 @@ static void 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) { /* @@ -347,7 +359,7 @@ advance_transition_function(AggState *aggstate, * 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; @@ -373,14 +385,13 @@ advance_transition_function(AggState *aggstate, /* * 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 @@ -390,7 +401,7 @@ advance_transition_function(AggState *aggstate, if (!peraggstate->transtypeByVal && DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue)) { - if (!fcinfo.isnull) + if (!fcinfo->isnull) { MemoryContextSwitchTo(aggstate->aggcontext); newVal = datumCopy(newVal, @@ -402,7 +413,7 @@ advance_transition_function(AggState *aggstate, } pergroupstate->transValue = newVal; - pergroupstate->transValueIsNull = fcinfo.isnull; + pergroupstate->transValueIsNull = fcinfo->isnull; MemoryContextSwitchTo(oldContext); } @@ -423,27 +434,46 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup) 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); } } } @@ -465,11 +495,15 @@ process_sorted_aggregate(AggState *aggstate, 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 @@ -477,13 +511,13 @@ process_sorted_aggregate(AggState *aggstate, */ 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; /* @@ -495,21 +529,21 @@ process_sorted_aggregate(AggState *aggstate, 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; } @@ -1286,7 +1320,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) 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; @@ -1297,6 +1332,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) *finalfnexpr; Datum textInitVal; int i; + ListCell *lc; /* Planner should have assigned aggregate to correct level */ Assert(aggref->agglevelsup == 0); @@ -1324,13 +1360,19 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* 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), @@ -1383,21 +1425,23 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) 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, @@ -1437,14 +1481,15 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* * 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", @@ -1458,14 +1503,25 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* 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; } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index a18f118bce4..f2b2afd81af 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -743,7 +743,7 @@ _copyAggref(Aggref *from) 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); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index a1b758e7495..0122ebd629c 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -156,7 +156,7 @@ _equalAggref(Aggref *a, Aggref *b) { 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); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 1b85b9a8063..61d49572ca8 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * 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* @@ -635,7 +635,7 @@ _outAggref(StringInfo str, Aggref *node) 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); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 883155dcd07..265a5b369ee 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * 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 @@ -348,7 +348,7 @@ _readAggref(void) 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); diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index 39e77897c0a..849b81a9a75 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -217,12 +217,13 @@ find_minmax_aggs_walker(Node *node, List **context) { 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); @@ -232,18 +233,19 @@ find_minmax_aggs_walker(Node *node, List **context) /* * 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); @@ -520,13 +522,14 @@ replace_aggs_with_params_mutator(Node *node, List **context) { 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"); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 0196e4b3ff0..dfc43149d3a 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * 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 @@ -397,17 +397,27 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) 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, @@ -423,17 +433,18 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) /* 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); } /* @@ -448,12 +459,12 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) 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; @@ -464,10 +475,10 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) } /* - * 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"))); @@ -3026,7 +3037,14 @@ expression_tree_walker(Node *node, /* 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; @@ -3448,7 +3466,7 @@ expression_tree_mutator(Node *node, Aggref *newnode; FLATCOPY(newnode, aggref, Aggref); - MUTATE(newnode->target, aggref->target, Expr *); + MUTATE(newnode->args, aggref->args, List *); return (Node *) newnode; } break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 754777c57bc..afd88e8125b 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * 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 @@ -7346,10 +7346,8 @@ func_expr: func_name '(' ')' | 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. * @@ -7358,12 +7356,8 @@ func_expr: func_name '(' ')' * 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; diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 6d381cd2d9e..3bda907c994 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -55,10 +55,10 @@ transformAggregateCall(ParseState *pstate, Aggref *agg) /* * 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 @@ -67,7 +67,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg) */ 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"))); @@ -360,7 +360,7 @@ check_ungrouped_columns_walker(Node *node, * (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). * @@ -371,7 +371,8 @@ check_ungrouped_columns_walker(Node *node, * *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, @@ -379,13 +380,9 @@ build_aggregate_fnexprs(Oid agg_input_type, 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 @@ -393,22 +390,21 @@ build_aggregate_fnexprs(Oid agg_input_type, * 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, @@ -425,11 +421,11 @@ build_aggregate_fnexprs(Oid agg_input_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, diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index aa0632a3898..b1b53164f80 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -259,10 +259,21 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, 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); @@ -1194,9 +1205,7 @@ LookupFuncNameTypeNames(List *funcname, List *argtypes, bool noError) * * 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) @@ -1204,7 +1213,7 @@ 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; @@ -1216,29 +1225,18 @@ LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError) 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); @@ -1247,7 +1245,7 @@ LookupAggNameTypeNames(List *aggname, List *argtypes, bool noError) { 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", diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 605efc60403..a2254b6e481 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -2,7 +2,7 @@ * 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" @@ -3880,15 +3880,29 @@ static void 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, ')'); } diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 19f2983424c..ea697739191 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12,7 +12,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -2325,7 +2325,8 @@ getAggregates(int *numAggs) int i_oid; int i_aggname; int i_aggnamespace; - int i_aggbasetype; + int i_pronargs; + int i_proargtypes; int i_rolname; int i_aggacl; @@ -2334,11 +2335,25 @@ getAggregates(int *numAggs) /* 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 " @@ -2351,7 +2366,8 @@ getAggregates(int *numAggs) { 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 " @@ -2365,7 +2381,8 @@ getAggregates(int *numAggs) "(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 " @@ -2386,7 +2403,8 @@ getAggregates(int *numAggs) 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"); @@ -2404,13 +2422,21 @@ getAggregates(int *numAggs) 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)); @@ -6759,6 +6785,7 @@ static char * format_aggregate_signature(AggInfo *agginfo, Archive *fout, bool honor_quotes) { PQExpBufferData buf; + int j; initPQExpBuffer(&buf); if (honor_quotes) @@ -6767,23 +6794,24 @@ format_aggregate_signature(AggInfo *agginfo, Archive *fout, bool 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; } @@ -6807,8 +6835,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) int i_aggsortop; int i_aggtranstype; int i_agginitval; - int i_anybasetype; - int i_fmtbasetype; int i_convertok; const char *aggtransfn; const char *aggfinalfn; @@ -6836,8 +6862,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) "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 " @@ -6850,8 +6874,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) "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 " @@ -6864,9 +6886,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) "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", @@ -6879,8 +6898,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) "(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", @@ -6904,8 +6921,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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); @@ -6913,10 +6928,6 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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); @@ -6932,27 +6943,20 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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", @@ -6986,8 +6990,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo) 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, @@ -7008,7 +7011,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 aggregates on ANY. + * syntax for zero-argument aggregates. */ free(aggsig); free(aggsig_tag); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 33b0fbf0589..738eff36ac8 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -6,7 +6,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -147,8 +147,7 @@ typedef struct _funcInfo 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 diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index ca7d01a6f88..4e4f2dd4b3e 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3,7 +3,7 @@ * * 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" @@ -67,17 +67,22 @@ describeAggregates(const char *pattern, bool verbose) 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, diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index bcb2232e0b1..00896bce2e2 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200607261 +#define CATALOG_VERSION_NO 200607271 #endif diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h index e9d5c5151ab..f77328b9e91 100644 --- a/src/include/catalog/pg_aggregate.h +++ b/src/include/catalog/pg_aggregate.h @@ -8,7 +8,7 @@ * 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 @@ -140,11 +140,9 @@ DATA(insert ( 2051 array_smaller - 1072 2277 _null_ )); 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}" )); @@ -214,7 +212,8 @@ DATA(insert ( 2243 bitor - 0 1560 _null_ )); */ extern void AggregateCreate(const char *aggName, Oid aggNamespace, - Oid aggBaseType, + Oid *aggArgTypes, + int numArgs, List *aggtransfnName, List *aggfinalfnName, List *aggsortopName, diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 7f68894d8d3..729f92d1bf4 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * 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 @@ -1534,6 +1534,8 @@ DESCR("truncate interval to specified units"); 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"); @@ -3148,7 +3150,9 @@ DATA(insert OID = 2051 ( min PGNSP PGUID 12 t f f f i 1 2277 "2277" _null_ _ 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_ )); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index e03daed8e7f..8dec4130e28 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -449,7 +449,7 @@ typedef struct GenericExprState 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; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index e289789de86..17c3a894b0b 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -184,9 +184,9 @@ typedef struct Aggref 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; diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h index 68163f1abaf..1486a3a0b2c 100644 --- a/src/include/parser/parse_agg.h +++ b/src/include/parser/parse_agg.h @@ -6,7 +6,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -19,7 +19,8 @@ extern void transformAggregateCall(ParseState *pstate, Aggref *agg); 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, diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 518315b3c1a..3b0c0f467a9 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -181,6 +181,7 @@ group by ten order by ten; 9 | 100 | 4 (10 rows) +-- user-defined aggregates SELECT newavg(four) AS avg_1 FROM onek; avg_1 -------------------- @@ -199,6 +200,24 @@ SELECT newcnt(four) AS cnt_1000 FROM onek; 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 diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out index b0fec460cbb..08daaa8ee3b 100644 --- a/src/test/regress/expected/create_aggregate.out +++ b/src/test/regress/expected/create_aggregate.out @@ -17,12 +17,29 @@ CREATE AGGREGATE newsum ( 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'; diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 1161d0474c4..5c905f55a54 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -51,7 +51,7 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR -- 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 @@ -67,11 +67,14 @@ 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 @@ -79,9 +82,10 @@ WHERE p1.oid != p2.oid AND 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. @@ -617,7 +621,7 @@ WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0; 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) @@ -648,13 +652,17 @@ FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr 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) diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out index 57a12583319..841d77c78e5 100644 --- a/src/test/regress/expected/polymorphism.out +++ b/src/test/regress/expected/polymorphism.out @@ -50,6 +50,9 @@ CREATE FUNCTION tf1p(anyarray,int) RETURNS anyarray AS -- 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; @@ -70,30 +73,30 @@ CREATE FUNCTION ffnp(int[]) returns int[] as -- ------- -- 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 @@ -148,13 +151,13 @@ ERROR: function tfp(integer[], anyelement) does not exist 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, @@ -170,21 +173,21 @@ ERROR: function tf2p(anyarray, anyelement) does not exist 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, @@ -205,30 +208,30 @@ CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp, -- ------- -- 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 @@ -282,21 +285,21 @@ ERROR: function tfp(integer[], anyelement) does not exist 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, @@ -318,13 +321,13 @@ ERROR: function tf2p(anyarray, anyelement) does not exist 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, @@ -335,6 +338,9 @@ ERROR: function tf1p(anyarray, anyelement) does not exist 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'); @@ -530,3 +536,9 @@ select f3, myaggn10a(f1) from t group by f3; a | {1,2,3} (3 rows) +select mysum2(f1, f1 + 1) from t; + mysum2 +-------- + 38 +(1 row) + diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index a9429525cab..1c2a6044500 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -48,11 +48,13 @@ group by ten order by ten; 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 diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql index 4188760c87c..891b0e0892b 100644 --- a/src/test/regress/sql/create_aggregate.sql +++ b/src/test/regress/sql/create_aggregate.sql @@ -20,12 +20,33 @@ CREATE AGGREGATE newsum ( 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'; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 7b1d2b54cba..84d0ce93b5c 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -55,7 +55,7 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR -- 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 @@ -69,12 +69,16 @@ 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 @@ -515,7 +519,7 @@ WHERE aggfnoid = 0 OR aggtransfn = 0 OR aggtranstype = 0; 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. @@ -539,13 +543,17 @@ FROM pg_aggregate AS a, pg_proc AS p, pg_proc AS ptr 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. diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql index 13d691a9e80..218a0a72b38 100644 --- a/src/test/regress/sql/polymorphism.sql +++ b/src/test/regress/sql/polymorphism.sql @@ -57,6 +57,10 @@ CREATE FUNCTION tf1p(anyarray,int) RETURNS anyarray AS 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; @@ -78,26 +82,26 @@ CREATE FUNCTION ffnp(int[]) returns int[] as -- ------- -- 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 = '{}'); @@ -207,26 +211,26 @@ CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp, -- ------- -- 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 = '{}'); @@ -330,6 +334,10 @@ CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p, 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'); @@ -365,3 +373,4 @@ select f3, myaggn08a(f1) from t group by f3; 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; |