From e3b1b6c0cdc1ceda26053573af1da4f35ee8566d Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 1 Jul 2003 19:10:53 +0000 Subject: [PATCH] Aggregates can be polymorphic, using polymorphic implementation functions. It also works to create a non-polymorphic aggregate from polymorphic functions, should you want to do that. Regression test added, docs still lacking. By Joe Conway, with some kibitzing from Tom Lane. --- src/backend/catalog/pg_aggregate.c | 142 +++++- src/backend/commands/aggregatecmds.c | 6 +- src/backend/executor/nodeAgg.c | 77 ++- src/backend/nodes/makefuncs.c | 23 +- src/backend/parser/parse_agg.c | 93 +++- src/backend/parser/parse_coerce.c | 101 ++-- src/backend/utils/cache/lsyscache.c | 32 +- src/include/nodes/makefuncs.h | 5 +- src/include/parser/parse_agg.h | 10 +- src/include/parser/parse_coerce.h | 5 +- src/include/utils/lsyscache.h | 3 +- src/test/regress/expected/polymorphism.out | 514 +++++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 3 +- src/test/regress/sql/polymorphism.sql | 367 +++++++++++++++ 15 files changed, 1300 insertions(+), 83 deletions(-) create mode 100644 src/test/regress/expected/polymorphism.out create mode 100644 src/test/regress/sql/polymorphism.sql diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index a2160fa025..ad348bf6a5 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/pg_aggregate.c,v 1.58 2003/06/25 21:30:25 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_aggregate.c,v 1.59 2003/07/01 19:10:52 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -29,6 +29,10 @@ #include "utils/syscache.h" +static Oid lookup_agg_function(List *fnName, int nargs, Oid *input_types, + Oid *rettype); + + /* * AggregateCreate */ @@ -48,9 +52,10 @@ AggregateCreate(const char *aggName, Form_pg_proc proc; Oid transfn; Oid finalfn = InvalidOid; /* can be omitted */ + Oid rettype; Oid finaltype; Oid fnArgs[FUNC_MAX_ARGS]; - int nargs; + int nargs_transfn; Oid procOid; TupleDesc tupDesc; int i; @@ -64,28 +69,49 @@ AggregateCreate(const char *aggName, if (!aggtransfnName) elog(ERROR, "aggregate must have a transition function"); + /* + * If transtype is polymorphic, basetype must be polymorphic also; + * else we will have no way to deduce the actual transtype. + */ + if ((aggTransType == ANYARRAYOID || aggTransType == ANYELEMENTOID) && + !(aggBaseType == ANYARRAYOID || aggBaseType == ANYELEMENTOID)) + elog(ERROR, "an aggregate using ANYARRAY or ANYELEMENT as trans type " + "must also have one of them as its base type"); + /* handle transfn */ MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid)); fnArgs[0] = aggTransType; if (aggBaseType == ANYOID) - nargs = 1; + nargs_transfn = 1; else { fnArgs[1] = aggBaseType; - nargs = 2; + nargs_transfn = 2; } - transfn = LookupFuncName(aggtransfnName, nargs, fnArgs); - if (!OidIsValid(transfn)) - func_error("AggregateCreate", aggtransfnName, nargs, fnArgs, NULL); + transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs, + &rettype); + + /* + * Return type of transfn (possibly after refinement by + * enforce_generic_type_consistency, if transtype isn't polymorphic) + * must exactly match declared transtype. + * + * In the non-polymorphic-transtype case, it might be okay to allow + * a rettype that's binary-coercible to transtype, but I'm not quite + * convinced that it's either safe or useful. When transtype is + * polymorphic we *must* demand exact equality. + */ + if (rettype != aggTransType) + elog(ERROR, "return type of transition function %s is not %s", + NameListToString(aggtransfnName), format_type_be(aggTransType)); + tup = SearchSysCache(PROCOID, ObjectIdGetDatum(transfn), 0, 0, 0); if (!HeapTupleIsValid(tup)) - func_error("AggregateCreate", aggtransfnName, nargs, fnArgs, NULL); + func_error("AggregateCreate", aggtransfnName, + nargs_transfn, fnArgs, NULL); proc = (Form_pg_proc) GETSTRUCT(tup); - if (proc->prorettype != aggTransType) - elog(ERROR, "return type of transition function %s is not %s", - NameListToString(aggtransfnName), format_type_be(aggTransType)); /* * If the transfn is strict and the initval is NULL, make sure input @@ -105,17 +131,8 @@ AggregateCreate(const char *aggName, { MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid)); fnArgs[0] = aggTransType; - finalfn = LookupFuncName(aggfinalfnName, 1, fnArgs); - if (!OidIsValid(finalfn)) - func_error("AggregateCreate", aggfinalfnName, 1, fnArgs, NULL); - tup = SearchSysCache(PROCOID, - ObjectIdGetDatum(finalfn), - 0, 0, 0); - if (!HeapTupleIsValid(tup)) - func_error("AggregateCreate", aggfinalfnName, 1, fnArgs, NULL); - proc = (Form_pg_proc) GETSTRUCT(tup); - finaltype = proc->prorettype; - ReleaseSysCache(tup); + finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs, + &finaltype); } else { @@ -126,6 +143,19 @@ AggregateCreate(const char *aggName, } Assert(OidIsValid(finaltype)); + /* + * If finaltype (i.e. aggregate return type) is polymorphic, + * basetype must be polymorphic also, else parser will fail to deduce + * result type. (Note: given the previous test on transtype and basetype, + * 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)) + elog(ERROR, "an aggregate returning ANYARRAY or ANYELEMENT " + "must also have one of them as its base type"); + /* * Everything looks okay. Try to create the pg_proc entry for the * aggregate. (This could fail if there's already a conflicting @@ -207,3 +237,71 @@ AggregateCreate(const char *aggName, recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } } + +/* + * lookup_agg_function -- common code for finding both transfn and finalfn + */ +static Oid +lookup_agg_function(List *fnName, + int nargs, + Oid *input_types, + Oid *rettype) +{ + Oid fnOid; + bool retset; + Oid *true_oid_array; + FuncDetailCode fdresult; + + /* + * func_get_detail looks up the function in the catalogs, does + * disambiguation for polymorphic functions, handles inheritance, and + * returns the funcid and type and set or singleton status of the + * function's return value. it also returns the true argument types + * to the function. + */ + fdresult = func_get_detail(fnName, NIL, nargs, input_types, + &fnOid, rettype, &retset, + &true_oid_array); + + /* only valid case is a normal function not returning a set */ + if (fdresult != FUNCDETAIL_NORMAL || + !OidIsValid(fnOid) || + retset) + func_error("AggregateCreate", fnName, nargs, input_types, NULL); + + /* + * 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))) + { + /* nothing to check here */ + } + else + { + *rettype = enforce_generic_type_consistency(input_types, + true_oid_array, + nargs, + *rettype); + } + + /* + * 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])) + func_error("AggregateCreate", fnName, nargs, input_types, NULL); + + if (nargs == 2 && + true_oid_array[1] != ANYARRAYOID && + true_oid_array[1] != ANYELEMENTOID && + !IsBinaryCoercible(input_types[1], true_oid_array[1])) + func_error("AggregateCreate", fnName, nargs, input_types, NULL); + + return fnOid; +} diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c index 1ea31a6a5a..860d490bd3 100644 --- a/src/backend/commands/aggregatecmds.c +++ b/src/backend/commands/aggregatecmds.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/aggregatecmds.c,v 1.8 2003/06/27 14:45:27 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/aggregatecmds.c,v 1.9 2003/07/01 19:10:52 tgl Exp $ * * DESCRIPTION * The "DefineFoo" routines take the parse tree and pick out the @@ -120,7 +120,9 @@ DefineAggregate(List *names, List *parameters) baseTypeId = typenameTypeId(baseType); transTypeId = typenameTypeId(transType); - if (get_typtype(transTypeId) == 'p') + if (get_typtype(transTypeId) == 'p' && + transTypeId != ANYARRAYOID && + transTypeId != ANYELEMENTOID) elog(ERROR, "Aggregate transition datatype cannot be %s", format_type_be(transTypeId)); diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 04d656f05a..b54d528eea 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -45,7 +45,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.109 2003/06/25 21:30:28 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.110 2003/07/01 19:10:52 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -59,6 +59,7 @@ #include "executor/nodeAgg.h" #include "miscadmin.h" #include "optimizer/clauses.h" +#include "parser/parse_agg.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_oper.h" @@ -1182,11 +1183,15 @@ ExecInitAgg(Agg *node, EState *estate) AggrefExprState *aggrefstate = (AggrefExprState *) lfirst(alist); Aggref *aggref = (Aggref *) aggrefstate->xprstate.expr; AggStatePerAgg peraggstate; + Oid inputType; HeapTuple aggTuple; Form_pg_aggregate aggform; + Oid aggtranstype; AclResult aclresult; Oid transfn_oid, finalfn_oid; + Expr *transfnexpr, + *finalfnexpr; Datum textInitVal; int i; @@ -1217,6 +1222,13 @@ ExecInitAgg(Agg *node, EState *estate) peraggstate->aggrefstate = aggrefstate; peraggstate->aggref = aggref; + /* + * 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. + */ + inputType = exprType((Node *) aggref->target); + aggTuple = SearchSysCache(AGGFNOID, ObjectIdGetDatum(aggref->aggfnoid), 0, 0, 0); @@ -1231,10 +1243,47 @@ ExecInitAgg(Agg *node, EState *estate) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, get_func_name(aggref->aggfnoid)); + peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn; + peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn; + + /* resolve actual type of transition state, if polymorphic */ + aggtranstype = aggform->aggtranstype; + if (aggtranstype == ANYARRAYOID || aggtranstype == ANYELEMENTOID) + { + /* have to fetch the agg's declared input type... */ + Oid agg_arg_types[FUNC_MAX_ARGS]; + 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]); + } + + /* build expression trees using actual argument & result types */ + build_aggregate_fnexprs(inputType, + aggtranstype, + aggref->aggtype, + transfn_oid, + finalfn_oid, + &transfnexpr, + &finalfnexpr); + + fmgr_info(transfn_oid, &peraggstate->transfn); + peraggstate->transfn.fn_expr = (Node *) transfnexpr; + + if (OidIsValid(finalfn_oid)) + { + fmgr_info(finalfn_oid, &peraggstate->finalfn); + peraggstate->finalfn.fn_expr = (Node *) finalfnexpr; + } + get_typlenbyval(aggref->aggtype, &peraggstate->resulttypeLen, &peraggstate->resulttypeByVal); - get_typlenbyval(aggform->aggtranstype, + get_typlenbyval(aggtranstype, &peraggstate->transtypeLen, &peraggstate->transtypeByVal); @@ -1250,14 +1299,7 @@ ExecInitAgg(Agg *node, EState *estate) peraggstate->initValue = (Datum) 0; else peraggstate->initValue = GetAggInitVal(textInitVal, - aggform->aggtranstype); - - peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn; - peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn; - - fmgr_info(transfn_oid, &peraggstate->transfn); - if (OidIsValid(finalfn_oid)) - fmgr_info(finalfn_oid, &peraggstate->finalfn); + aggtranstype); /* * If the transfn is strict and the initval is NULL, make sure @@ -1268,26 +1310,13 @@ ExecInitAgg(Agg *node, EState *estate) */ if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull) { - /* - * Note: use the type from the input expression here, not from - * pg_proc.proargtypes, because the latter might be 0. - * (Consider COUNT(*).) - */ - Oid inputType = exprType((Node *) aggref->target); - - if (!IsBinaryCoercible(inputType, aggform->aggtranstype)) + if (!IsBinaryCoercible(inputType, aggtranstype)) elog(ERROR, "Aggregate %u needs to have compatible input type and transition type", aggref->aggfnoid); } if (aggref->aggdistinct) { - /* - * Note: use the type from the input expression here, not from - * pg_proc.proargtypes, because the latter might be a pseudotype. - * (Consider COUNT(*).) - */ - Oid inputType = exprType((Node *) aggref->target); Oid eq_function; /* We don't implement DISTINCT aggs in the HASHED case */ diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 4683d36bfb..38bd4ac6f6 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/makefuncs.c,v 1.39 2003/05/06 00:20:32 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/makefuncs.c,v 1.40 2003/07/01 19:10:52 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -251,3 +251,24 @@ makeTypeName(char *typnam) n->typmod = -1; return n; } + +/* + * makeFuncExpr - + * build an expression tree representing a function call. + * + * The argument expressions must have been transformed already. + */ +FuncExpr * +makeFuncExpr(Oid funcid, Oid rettype, List *args, CoercionForm fformat) +{ + FuncExpr *funcexpr; + + funcexpr = makeNode(FuncExpr); + funcexpr->funcid = funcid; + funcexpr->funcresulttype = rettype; + funcexpr->funcretset = false; /* only allowed case here */ + funcexpr->funcformat = fformat; + funcexpr->args = args; + + return funcexpr; +} diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 49d952bf8a..306a64735b 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -8,18 +8,21 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.53 2003/06/06 15:04:02 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.54 2003/07/01 19:10:52 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "nodes/makefuncs.h" +#include "nodes/params.h" #include "optimizer/clauses.h" #include "optimizer/tlist.h" #include "optimizer/var.h" #include "parser/parse_agg.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" +#include "utils/lsyscache.h" typedef struct @@ -312,3 +315,91 @@ check_ungrouped_columns_walker(Node *node, return expression_tree_walker(node, check_ungrouped_columns_walker, (void *) context); } + +/* + * Create expression trees for the transition and final functions + * of an aggregate. These are needed so that polymorphic functions + * can be used within an aggregate --- without the expression trees, + * such functions would not know the datatypes they are supposed to use. + * (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, + * transition, and result types of the aggregate. These should all be + * resolved to actual types (ie, none should ever be ANYARRAY or ANYELEMENT). + * + * transfn_oid and finalfn_oid identify the funcs to be called; the latter + * may be InvalidOid. + * + * Pointers to the constructed trees are returned into *transfnexpr and + * *finalfnexpr. The latter is set to NULL if there's no finalfn. + */ +void +build_aggregate_fnexprs(Oid agg_input_type, + Oid agg_state_type, + Oid agg_result_type, + Oid transfn_oid, + Oid finalfn_oid, + Expr **transfnexpr, + Expr **finalfnexpr) +{ + Oid transfn_arg_types[FUNC_MAX_ARGS]; + int transfn_nargs; + Param *arg0; + Param *arg1; + List *args; + + /* get the transition function signature (only need nargs) */ + (void) get_func_signature(transfn_oid, transfn_arg_types, &transfn_nargs); + + /* + * Build arg list to use in the transfn FuncExpr node. We really + * only care that transfn can discover the actual argument types + * at runtime using get_fn_expr_argtype(), so it's okay to use + * Param nodes that don't correspond to any real Param. + */ + arg0 = makeNode(Param); + arg0->paramkind = PARAM_EXEC; + arg0->paramid = -1; + arg0->paramtype = agg_state_type; + + if (transfn_nargs == 2) + { + arg1 = makeNode(Param); + arg1->paramkind = PARAM_EXEC; + arg1->paramid = -1; + arg1->paramtype = agg_input_type; + + args = makeList2(arg0, arg1); + } + else + { + args = makeList1(arg0); + } + + *transfnexpr = (Expr *) makeFuncExpr(transfn_oid, + agg_state_type, + args, + COERCE_DONTCARE); + + /* see if we have a final function */ + if (!OidIsValid(finalfn_oid)) + { + *finalfnexpr = NULL; + return; + } + + /* + * Build expr tree for final function + */ + arg0 = makeNode(Param); + arg0->paramkind = PARAM_EXEC; + arg0->paramid = -1; + arg0->paramtype = agg_state_type; + args = makeList1(arg0); + + *finalfnexpr = (Expr *) makeFuncExpr(finalfn_oid, + agg_result_type, + args, + COERCE_DONTCARE); +} diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index eba6f5a133..977780fecc 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.101 2003/06/27 00:33:25 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.102 2003/07/01 19:10:53 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,8 +32,6 @@ static Node *coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, CoercionForm cformat, bool isExplicit); -static Node *build_func_call(Oid funcid, Oid rettype, List *args, - CoercionForm fformat); /* @@ -275,8 +273,9 @@ coerce_type(ParseState *pstate, Node *node, */ Oid baseTypeId = getBaseType(targetTypeId); - result = build_func_call(funcId, baseTypeId, makeList1(node), - cformat); + result = (Node *) makeFuncExpr(funcId, baseTypeId, + makeList1(node), + cformat); /* * If domain, coerce to the domain type and relabel with @@ -534,7 +533,7 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, args = lappend(args, cons); } - node = build_func_call(funcId, targetTypeId, args, cformat); + node = (Node *) makeFuncExpr(funcId, targetTypeId, args, cformat); } return node; @@ -935,6 +934,76 @@ enforce_generic_type_consistency(Oid *actual_arg_types, return rettype; } +/* + * resolve_generic_type() + * Deduce an individual actual datatype on the assumption that + * the rules for ANYARRAY/ANYELEMENT are being followed. + * + * declared_type is the declared datatype we want to resolve. + * context_actual_type is the actual input datatype to some argument + * that has declared datatype context_declared_type. + * + * If declared_type isn't polymorphic, we just return it. Otherwise, + * context_declared_type must be polymorphic, and we deduce the correct + * return type based on the relationship of the two polymorphic types. + */ +Oid +resolve_generic_type(Oid declared_type, + Oid context_actual_type, + Oid context_declared_type) +{ + if (declared_type == ANYARRAYOID) + { + if (context_declared_type == ANYARRAYOID) + { + /* Use actual type, but it must be an array */ + Oid array_typelem = get_element_type(context_actual_type); + + if (!OidIsValid(array_typelem)) + elog(ERROR, "Argument declared ANYARRAY is not an array: %s", + format_type_be(context_actual_type)); + return context_actual_type; + } + else if (context_declared_type == ANYELEMENTOID) + { + /* Use the array type corresponding to actual type */ + Oid array_typeid = get_array_type(context_actual_type); + + if (!OidIsValid(array_typeid)) + elog(ERROR, "Cannot find array type for datatype %s", + format_type_be(context_actual_type)); + return array_typeid; + } + } + else if (declared_type == ANYELEMENTOID) + { + if (context_declared_type == ANYARRAYOID) + { + /* Use the element type corresponding to actual type */ + Oid array_typelem = get_element_type(context_actual_type); + + if (!OidIsValid(array_typelem)) + elog(ERROR, "Argument declared ANYARRAY is not an array: %s", + format_type_be(context_actual_type)); + return array_typelem; + } + else if (context_declared_type == ANYELEMENTOID) + { + /* Use the actual type; it doesn't matter if array or not */ + return context_actual_type; + } + } + else + { + /* declared_type isn't polymorphic, so return it as-is */ + return declared_type; + } + /* If we get here, declared_type is polymorphic and context isn't */ + /* NB: this is a calling-code logic error, not a user error */ + elog(ERROR, "Cannot determine ANYARRAY/ANYELEMENT type because context isn't polymorphic"); + return InvalidOid; /* keep compiler quiet */ +} + /* TypeCategory() * Assign a category to the specified type OID. @@ -1417,23 +1486,3 @@ find_typmod_coercion_function(Oid typeId, int *nargs) return funcid; } - -/* - * Build an expression tree representing a function call. - * - * The argument expressions must have been transformed already. - */ -static Node * -build_func_call(Oid funcid, Oid rettype, List *args, CoercionForm fformat) -{ - FuncExpr *funcexpr; - - funcexpr = makeNode(FuncExpr); - funcexpr->funcid = funcid; - funcexpr->funcresulttype = rettype; - funcexpr->funcretset = false; /* only possible case here */ - funcexpr->funcformat = fformat; - funcexpr->args = args; - - return (Node *) funcexpr; -} diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 6da53870c4..879d2ba57a 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.100 2003/06/27 00:33:25 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.101 2003/07/01 19:10:53 tgl Exp $ * * NOTES * Eventually, the index information should go through here, too. @@ -718,6 +718,36 @@ get_func_rettype(Oid funcid) return result; } +/* + * get_func_signature + * Given procedure id, return the function's argument and result types. + * (The return value is the result type.) + * + * argtypes must point to a vector of size FUNC_MAX_ARGS. + */ +Oid +get_func_signature(Oid funcid, Oid *argtypes, int *nargs) +{ + HeapTuple tp; + Form_pg_proc procstruct; + Oid result; + + tp = SearchSysCache(PROCOID, + ObjectIdGetDatum(funcid), + 0, 0, 0); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "Function OID %u does not exist", funcid); + + procstruct = (Form_pg_proc) GETSTRUCT(tp); + + result = procstruct->prorettype; + memcpy(argtypes, procstruct->proargtypes, FUNC_MAX_ARGS * sizeof(Oid)); + *nargs = (int) procstruct->pronargs; + + ReleaseSysCache(tp); + return result; +} + /* * get_func_retset * Given procedure id, return the function's proretset flag. diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index a66e9fc09b..f1b45e897b 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: makefuncs.h,v 1.44 2003/02/10 04:44:46 tgl Exp $ + * $Id: makefuncs.h,v 1.45 2003/07/01 19:10:53 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -56,4 +56,7 @@ extern RangeVar *makeRangeVar(char *schemaname, char *relname); extern TypeName *makeTypeName(char *typnam); +extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, + List *args, CoercionForm fformat); + #endif /* MAKEFUNC_H */ diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h index bc1e601cc2..79f226fb89 100644 --- a/src/include/parser/parse_agg.h +++ b/src/include/parser/parse_agg.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parse_agg.h,v 1.26 2003/06/06 15:04:03 tgl Exp $ + * $Id: parse_agg.h,v 1.27 2003/07/01 19:10:53 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,4 +19,12 @@ extern void transformAggregateCall(ParseState *pstate, Aggref *agg); extern void parseCheckAggregates(ParseState *pstate, Query *qry); +extern void build_aggregate_fnexprs(Oid agg_input_type, + Oid agg_state_type, + Oid agg_result_type, + Oid transfn_oid, + Oid finalfn_oid, + Expr **transfnexpr, + Expr **finalfnexpr); + #endif /* PARSE_AGG_H */ diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index de2ea95db8..5facbc7d7d 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parse_coerce.h,v 1.51 2003/04/29 22:13:11 tgl Exp $ + * $Id: parse_coerce.h,v 1.52 2003/07/01 19:10:53 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -67,6 +67,9 @@ extern Oid enforce_generic_type_consistency(Oid *actual_arg_types, Oid *declared_arg_types, int nargs, Oid rettype); +extern Oid resolve_generic_type(Oid declared_type, + Oid context_actual_type, + Oid context_declared_type); extern bool find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, CoercionContext ccontext, diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 4d8749befc..1f8bcb06d1 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: lsyscache.h,v 1.75 2003/06/27 00:33:26 tgl Exp $ + * $Id: lsyscache.h,v 1.76 2003/07/01 19:10:53 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -50,6 +50,7 @@ extern RegProcedure get_oprrest(Oid opno); extern RegProcedure get_oprjoin(Oid opno); extern char *get_func_name(Oid funcid); extern Oid get_func_rettype(Oid funcid); +extern Oid get_func_signature(Oid funcid, Oid *argtypes, int *nargs); extern bool get_func_retset(Oid funcid); extern bool func_strict(Oid funcid); extern char func_volatile(Oid funcid); diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out new file mode 100644 index 0000000000..63dfdb694f --- /dev/null +++ b/src/test/regress/expected/polymorphism.out @@ -0,0 +1,514 @@ +-- Currently this tests polymorphic aggregates and indirectly does some +-- testing of polymorphic SQL functions. It ought to be extended. +-- Legend: +----------- +-- A = type is ANY +-- P = type is polymorphic +-- N = type is non-polymorphic +-- B = aggregate base type +-- S = aggregate state type +-- R = aggregate return type +-- 1 = arg1 of a function +-- 2 = arg2 of a function +-- ag = aggregate +-- tf = trans (state) function +-- ff = final function +-- rt = return type of a function +-- -> = implies +-- => = allowed +-- !> = not allowed +-- E = exists +-- NE = not-exists +-- +-- Possible states: +-- ---------------- +-- B = (A || P || N) +-- when (B = A) -> (tf2 = NE) +-- S = (P || N) +-- ff = (E || NE) +-- tf1 = (P || N) +-- tf2 = (NE || P || N) +-- R = (P || N) +-- create functions for use as tf and ff with the needed combinations of +-- argument polymorphism, but within the constraints of valid aggregate +-- functions, i.e. tf arg1 and tf return type must match +-- polymorphic single arg transfn +CREATE FUNCTION stfp(anyarray) returns anyarray as +'select $1' language 'sql'; +-- non-polymorphic single arg transfn +CREATE FUNCTION stfnp(int[]) returns int[] as +'select $1' language 'sql'; +-- dual polymorphic transfn +CREATE FUNCTION tfp(anyarray,anyelement) returns anyarray as +'select $1 || $2' language 'sql'; +-- dual non-polymorphic transfn +CREATE FUNCTION tfnp(int[],int) returns int[] as +'select $1 || $2' language 'sql'; +-- arg1 only polymorphic transfn +CREATE FUNCTION tf1p(anyarray,int) returns anyarray as +'select $1' language 'sql'; +-- arg2 only polymorphic transfn +CREATE FUNCTION tf2p(int[],anyelement) returns int[] as +'select $1' language 'sql'; +-- finalfn polymorphic +CREATE FUNCTION ffp(anyarray) returns anyarray as +'select $1' language 'sql'; +-- finalfn non-polymorphic +CREATE FUNCTION ffnp(int[]) returns int[] as +'select $1' language 'sql'; +-- Try to cover all the possible states: +-- +-- Note: in Cases 1 & 2, we are trying to return P. Therefore, if the transfn +-- is stfnp, tfnp, or tf2p, we must use ffp as finalfn, because stfnp, tfnp, +-- and tf2p do not return P. Conversely, in Cases 3 & 4, we are trying to +-- return N. Therefore, if the transfn is stfp, tfp, or tf1p, we must use ffnp +-- as finalfn, because stfp, tfp, and tf1p do not return N. +-- +-- Case1 (R = P) && (B = A) +-- ------------------------ +-- S tf1 +-- ------- +-- N N +-- should CREATE +CREATE AGGREGATE myaggp01a(BASETYPE = "ANY", 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, + FINALFUNC = ffp, INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +-- N P +-- should CREATE +CREATE AGGREGATE myaggp03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp03b(BASETYPE = "ANY", 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, + FINALFUNC = ffp, INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +CREATE AGGREGATE myaggp04b(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, + INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +-- Case2 (R = P) && ((B = P) || (B = N)) +-- ------------------------------------- +-- S tf1 B tf2 +-- ----------------------- +-- N N N N +-- should CREATE +CREATE AGGREGATE myaggp05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +-- N N N P +-- should CREATE +CREATE AGGREGATE myaggp06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +-- N N P N +-- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int) +CREATE AGGREGATE myaggp07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +ERROR: AggregateCreate: function tfnp(integer[], anyelement) does not exist +-- N N P P +-- should CREATE +CREATE AGGREGATE myaggp08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +-- N P N N +-- should CREATE +CREATE AGGREGATE myaggp09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp09b(BASETYPE = int, SFUNC = tf1p, STYPE = int[], + INITCOND = '{}'); +-- N P N P +-- should CREATE +CREATE AGGREGATE myaggp10a(BASETYPE = int, SFUNC = tfp, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp10b(BASETYPE = int, SFUNC = tfp, STYPE = int[], + INITCOND = '{}'); +-- N P P N +-- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int) +CREATE AGGREGATE myaggp11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +ERROR: AggregateCreate: function tf1p(integer[], anyelement) does not exist +CREATE AGGREGATE myaggp11b(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[], + INITCOND = '{}'); +ERROR: AggregateCreate: function tf1p(integer[], anyelement) does not exist +-- N P P P +-- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement) +CREATE AGGREGATE myaggp12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +ERROR: AggregateCreate: function tfp(integer[], anyelement) does not exist +CREATE AGGREGATE myaggp12b(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[], + INITCOND = '{}'); +ERROR: AggregateCreate: function tfp(integer[], anyelement) does not exist +-- P N N N +-- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int) +CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, + FINALFUNC = ffp, INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base 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: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +-- P N P N +-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) +CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp, + STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); +ERROR: AggregateCreate: function tfnp(anyarray, anyelement) does not exist +-- P N P P +-- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement) +CREATE AGGREGATE myaggp16a(BASETYPE = anyelement, SFUNC = tf2p, + STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); +ERROR: AggregateCreate: function tf2p(anyarray, anyelement) does not exist +-- P P N N +-- should ERROR: we have no way to resolve S +CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, + FINALFUNC = ffp, INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, + INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base 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: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, + INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +-- P P P N +-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) +CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p, + STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); +ERROR: AggregateCreate: function tf1p(anyarray, anyelement) does not exist +CREATE AGGREGATE myaggp19b(BASETYPE = anyelement, SFUNC = tf1p, + STYPE = anyarray, INITCOND = '{}'); +ERROR: AggregateCreate: function tf1p(anyarray, anyelement) does not exist +-- P P P P +-- should CREATE +CREATE AGGREGATE myaggp20a(BASETYPE = anyelement, SFUNC = tfp, + STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp, + STYPE = anyarray, INITCOND = '{}'); +-- Case3 (R = N) && (B = A) +-- ------------------------ +-- S tf1 +-- ------- +-- N N +-- should CREATE +CREATE AGGREGATE myaggn01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[], + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn01b(BASETYPE = "ANY", 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, + FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +CREATE AGGREGATE myaggn02b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, + INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +-- N P +-- should CREATE +CREATE AGGREGATE myaggn03a(BASETYPE = "ANY", 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, + FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +-- Case4 (R = N) && ((B = P) || (B = N)) +-- ------------------------------------- +-- S tf1 B tf2 +-- ----------------------- +-- N N N N +-- should CREATE +CREATE AGGREGATE myaggn05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn05b(BASETYPE = int, SFUNC = tfnp, STYPE = int[], + INITCOND = '{}'); +-- N N N P +-- should CREATE +CREATE AGGREGATE myaggn06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn06b(BASETYPE = int, SFUNC = tf2p, STYPE = int[], + INITCOND = '{}'); +-- N N P N +-- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int) +CREATE AGGREGATE myaggn07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: AggregateCreate: function tfnp(integer[], anyelement) does not exist +CREATE AGGREGATE myaggn07b(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[], + INITCOND = '{}'); +ERROR: AggregateCreate: function tfnp(integer[], anyelement) does not exist +-- N N P P +-- should CREATE +CREATE AGGREGATE myaggn08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn08b(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[], + INITCOND = '{}'); +-- N P N N +-- should CREATE +CREATE AGGREGATE myaggn09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +-- N P N P +-- should CREATE +CREATE AGGREGATE myaggn10a(BASETYPE = int, SFUNC = tfp, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +-- N P P N +-- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int) +CREATE AGGREGATE myaggn11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: AggregateCreate: function tf1p(integer[], anyelement) does not exist +-- N P P P +-- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement) +CREATE AGGREGATE myaggn12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: AggregateCreate: function tfp(integer[], anyelement) does not exist +-- P N N N +-- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int) +CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, + FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, + INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base 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: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, + INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +-- P N P N +-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) +CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp, + STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: AggregateCreate: function tfnp(anyarray, anyelement) does not exist +CREATE AGGREGATE myaggn15b(BASETYPE = anyelement, SFUNC = tfnp, + STYPE = anyarray, INITCOND = '{}'); +ERROR: AggregateCreate: function tfnp(anyarray, anyelement) does not exist +-- P N P P +-- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement) +CREATE AGGREGATE myaggn16a(BASETYPE = anyelement, SFUNC = tf2p, + STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: AggregateCreate: function tf2p(anyarray, anyelement) does not exist +CREATE AGGREGATE myaggn16b(BASETYPE = anyelement, SFUNC = tf2p, + STYPE = anyarray, INITCOND = '{}'); +ERROR: AggregateCreate: function tf2p(anyarray, anyelement) does not exist +-- P P N N +-- should ERROR: ffnp(anyarray) not matched by ffnp(int[]) +CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, + FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base 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: an aggregate using ANYARRAY or ANYELEMENT as trans type must also have one of them as its base type +-- P P P N +-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) +CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p, + STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: AggregateCreate: function tf1p(anyarray, anyelement) does not exist +-- P P P P +-- should ERROR: ffnp(anyarray) not matched by ffnp(int[]) +CREATE AGGREGATE myaggn20a(BASETYPE = anyelement, SFUNC = tfp, + STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); +ERROR: AggregateCreate: function ffnp(anyarray) does not exist +-- create test data for polymorphic aggregates +create temp table t(f1 int, f2 int[], f3 text); +insert into t values(1,array[1],'a'); +insert into t values(1,array[11],'b'); +insert into t values(1,array[111],'c'); +insert into t values(2,array[2],'a'); +insert into t values(2,array[22],'b'); +insert into t values(2,array[222],'c'); +insert into t values(3,array[3],'a'); +insert into t values(3,array[3],'b'); +-- test the successfully created polymorphic aggregates +select f3, myaggp01a(*) from t group by f3; + f3 | myaggp01a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggp03a(*) from t group by f3; + f3 | myaggp03a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggp03b(*) from t group by f3; + f3 | myaggp03b +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggp05a(f1) from t group by f3; + f3 | myaggp05a +----+----------- + b | {1,2,3} + a | {1,2,3} + c | {1,2} +(3 rows) + +select f3, myaggp06a(f1) from t group by f3; + f3 | myaggp06a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggp08a(f1) from t group by f3; + f3 | myaggp08a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggp09a(f1) from t group by f3; + f3 | myaggp09a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggp09b(f1) from t group by f3; + f3 | myaggp09b +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggp10a(f1) from t group by f3; + f3 | myaggp10a +----+----------- + b | {1,2,3} + a | {1,2,3} + c | {1,2} +(3 rows) + +select f3, myaggp10b(f1) from t group by f3; + f3 | myaggp10b +----+----------- + b | {1,2,3} + a | {1,2,3} + c | {1,2} +(3 rows) + +select f3, myaggp20a(f1) from t group by f3; + f3 | myaggp20a +----+----------- + b | {1,2,3} + a | {1,2,3} + c | {1,2} +(3 rows) + +select f3, myaggp20b(f1) from t group by f3; + f3 | myaggp20b +----+----------- + b | {1,2,3} + a | {1,2,3} + c | {1,2} +(3 rows) + +select f3, myaggn01a(*) from t group by f3; + f3 | myaggn01a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggn01b(*) from t group by f3; + f3 | myaggn01b +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggn03a(*) from t group by f3; + f3 | myaggn03a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggn05a(f1) from t group by f3; + f3 | myaggn05a +----+----------- + b | {1,2,3} + a | {1,2,3} + c | {1,2} +(3 rows) + +select f3, myaggn05b(f1) from t group by f3; + f3 | myaggn05b +----+----------- + b | {1,2,3} + a | {1,2,3} + c | {1,2} +(3 rows) + +select f3, myaggn06a(f1) from t group by f3; + f3 | myaggn06a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggn06b(f1) from t group by f3; + f3 | myaggn06b +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggn08a(f1) from t group by f3; + f3 | myaggn08a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggn08b(f1) from t group by f3; + f3 | myaggn08b +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggn09a(f1) from t group by f3; + f3 | myaggn09a +----+----------- + b | {} + a | {} + c | {} +(3 rows) + +select f3, myaggn10a(f1) from t group by f3; + f3 | myaggn10a +----+----------- + b | {1,2,3} + a | {1,2,3} + c | {1,2} +(3 rows) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index e8e8d1e67b..913c4cba0c 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -74,4 +74,4 @@ test: select_views portals_p2 rules foreign_key cluster # The sixth group of parallel test # ---------- # "plpgsql" cannot run concurrently with "rules" -test: limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence +test: limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index ff2a0d1662..d3ae9f5ddb 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -1,4 +1,4 @@ -# $Header: /cvsroot/pgsql/src/test/regress/serial_schedule,v 1.19 2003/03/20 07:02:11 momjian Exp $ +# $Header: /cvsroot/pgsql/src/test/regress/serial_schedule,v 1.20 2003/07/01 19:10:53 tgl Exp $ # This should probably be in an order similar to parallel_schedule. test: boolean test: char @@ -91,3 +91,4 @@ test: conversion test: truncate test: alter_table test: sequence +test: polymorphism diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql new file mode 100644 index 0000000000..5175f9f93d --- /dev/null +++ b/src/test/regress/sql/polymorphism.sql @@ -0,0 +1,367 @@ +-- Currently this tests polymorphic aggregates and indirectly does some +-- testing of polymorphic SQL functions. It ought to be extended. + + +-- Legend: +----------- +-- A = type is ANY +-- P = type is polymorphic +-- N = type is non-polymorphic +-- B = aggregate base type +-- S = aggregate state type +-- R = aggregate return type +-- 1 = arg1 of a function +-- 2 = arg2 of a function +-- ag = aggregate +-- tf = trans (state) function +-- ff = final function +-- rt = return type of a function +-- -> = implies +-- => = allowed +-- !> = not allowed +-- E = exists +-- NE = not-exists +-- +-- Possible states: +-- ---------------- +-- B = (A || P || N) +-- when (B = A) -> (tf2 = NE) +-- S = (P || N) +-- ff = (E || NE) +-- tf1 = (P || N) +-- tf2 = (NE || P || N) +-- R = (P || N) + +-- create functions for use as tf and ff with the needed combinations of +-- argument polymorphism, but within the constraints of valid aggregate +-- functions, i.e. tf arg1 and tf return type must match + +-- polymorphic single arg transfn +CREATE FUNCTION stfp(anyarray) returns anyarray as +'select $1' language 'sql'; +-- non-polymorphic single arg transfn +CREATE FUNCTION stfnp(int[]) returns int[] as +'select $1' language 'sql'; + +-- dual polymorphic transfn +CREATE FUNCTION tfp(anyarray,anyelement) returns anyarray as +'select $1 || $2' language 'sql'; +-- dual non-polymorphic transfn +CREATE FUNCTION tfnp(int[],int) returns int[] as +'select $1 || $2' language 'sql'; + +-- arg1 only polymorphic transfn +CREATE FUNCTION tf1p(anyarray,int) returns anyarray as +'select $1' language 'sql'; +-- arg2 only polymorphic transfn +CREATE FUNCTION tf2p(int[],anyelement) returns int[] as +'select $1' language 'sql'; + +-- finalfn polymorphic +CREATE FUNCTION ffp(anyarray) returns anyarray as +'select $1' language 'sql'; +-- finalfn non-polymorphic +CREATE FUNCTION ffnp(int[]) returns int[] as +'select $1' language 'sql'; + +-- Try to cover all the possible states: +-- +-- Note: in Cases 1 & 2, we are trying to return P. Therefore, if the transfn +-- is stfnp, tfnp, or tf2p, we must use ffp as finalfn, because stfnp, tfnp, +-- and tf2p do not return P. Conversely, in Cases 3 & 4, we are trying to +-- return N. Therefore, if the transfn is stfp, tfp, or tf1p, we must use ffnp +-- as finalfn, because stfp, tfp, and tf1p do not return N. +-- +-- Case1 (R = P) && (B = A) +-- ------------------------ +-- S tf1 +-- ------- +-- N N +-- should CREATE +CREATE AGGREGATE myaggp01a(BASETYPE = "ANY", 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, + FINALFUNC = ffp, INITCOND = '{}'); + +-- N P +-- should CREATE +CREATE AGGREGATE myaggp03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp03b(BASETYPE = "ANY", 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, + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp04b(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, + INITCOND = '{}'); + + +-- Case2 (R = P) && ((B = P) || (B = N)) +-- ------------------------------------- +-- S tf1 B tf2 +-- ----------------------- +-- N N N N +-- should CREATE +CREATE AGGREGATE myaggp05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); + +-- N N N P +-- should CREATE +CREATE AGGREGATE myaggp06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); + +-- N N P N +-- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int) +CREATE AGGREGATE myaggp07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); + +-- N N P P +-- should CREATE +CREATE AGGREGATE myaggp08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); + +-- N P N N +-- should CREATE +CREATE AGGREGATE myaggp09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp09b(BASETYPE = int, SFUNC = tf1p, STYPE = int[], + INITCOND = '{}'); + +-- N P N P +-- should CREATE +CREATE AGGREGATE myaggp10a(BASETYPE = int, SFUNC = tfp, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp10b(BASETYPE = int, SFUNC = tfp, STYPE = int[], + INITCOND = '{}'); + +-- N P P N +-- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int) +CREATE AGGREGATE myaggp11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp11b(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[], + INITCOND = '{}'); + +-- N P P P +-- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement) +CREATE AGGREGATE myaggp12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[], + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp12b(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[], + INITCOND = '{}'); + +-- P N N N +-- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int) +CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, + FINALFUNC = ffp, INITCOND = '{}'); + +-- 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 = '{}'); + +-- P N P N +-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) +CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp, + STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); + +-- P N P P +-- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement) +CREATE AGGREGATE myaggp16a(BASETYPE = anyelement, SFUNC = tf2p, + STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); + +-- P P N N +-- should ERROR: we have no way to resolve S +CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, + FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, + INITCOND = '{}'); + +-- 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 = '{}'); +CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, + INITCOND = '{}'); + +-- P P P N +-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) +CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p, + STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp19b(BASETYPE = anyelement, SFUNC = tf1p, + STYPE = anyarray, INITCOND = '{}'); + +-- P P P P +-- should CREATE +CREATE AGGREGATE myaggp20a(BASETYPE = anyelement, SFUNC = tfp, + STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); +CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp, + STYPE = anyarray, INITCOND = '{}'); + +-- Case3 (R = N) && (B = A) +-- ------------------------ +-- S tf1 +-- ------- +-- N N +-- should CREATE +CREATE AGGREGATE myaggn01a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[], + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn01b(BASETYPE = "ANY", 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, + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn02b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, + INITCOND = '{}'); + +-- N P +-- should CREATE +CREATE AGGREGATE myaggn03a(BASETYPE = "ANY", 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, + FINALFUNC = ffnp, INITCOND = '{}'); + + +-- Case4 (R = N) && ((B = P) || (B = N)) +-- ------------------------------------- +-- S tf1 B tf2 +-- ----------------------- +-- N N N N +-- should CREATE +CREATE AGGREGATE myaggn05a(BASETYPE = int, SFUNC = tfnp, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn05b(BASETYPE = int, SFUNC = tfnp, STYPE = int[], + INITCOND = '{}'); + +-- N N N P +-- should CREATE +CREATE AGGREGATE myaggn06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn06b(BASETYPE = int, SFUNC = tf2p, STYPE = int[], + INITCOND = '{}'); + +-- N N P N +-- should ERROR: tfnp(int[], anyelement) not matched by tfnp(int[], int) +CREATE AGGREGATE myaggn07a(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn07b(BASETYPE = anyelement, SFUNC = tfnp, STYPE = int[], + INITCOND = '{}'); + +-- N N P P +-- should CREATE +CREATE AGGREGATE myaggn08a(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn08b(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[], + INITCOND = '{}'); + +-- N P N N +-- should CREATE +CREATE AGGREGATE myaggn09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); + +-- N P N P +-- should CREATE +CREATE AGGREGATE myaggn10a(BASETYPE = int, SFUNC = tfp, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); + +-- N P P N +-- should ERROR: tf1p(int[],anyelement) not matched by tf1p(anyarray,int) +CREATE AGGREGATE myaggn11a(BASETYPE = anyelement, SFUNC = tf1p, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); + +-- N P P P +-- should ERROR: tfp(int[],anyelement) not matched by tfp(anyarray,anyelement) +CREATE AGGREGATE myaggn12a(BASETYPE = anyelement, SFUNC = tfp, STYPE = int[], + FINALFUNC = ffnp, INITCOND = '{}'); + +-- P N N N +-- should ERROR: tfnp(anyarray, int) not matched by tfnp(int[],int) +CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, + FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, + INITCOND = '{}'); + +-- 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 = '{}'); +CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, + INITCOND = '{}'); + +-- P N P N +-- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) +CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp, + STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn15b(BASETYPE = anyelement, SFUNC = tfnp, + STYPE = anyarray, INITCOND = '{}'); + +-- P N P P +-- should ERROR: tf2p(anyarray, anyelement) not matched by tf2p(int[],anyelement) +CREATE AGGREGATE myaggn16a(BASETYPE = anyelement, SFUNC = tf2p, + STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); +CREATE AGGREGATE myaggn16b(BASETYPE = anyelement, SFUNC = tf2p, + STYPE = anyarray, INITCOND = '{}'); + +-- P P N N +-- should ERROR: ffnp(anyarray) not matched by ffnp(int[]) +CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, + FINALFUNC = ffnp, INITCOND = '{}'); + +-- 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 = '{}'); + +-- P P P N +-- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) +CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p, + STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); + +-- P P P P +-- should ERROR: ffnp(anyarray) not matched by ffnp(int[]) +CREATE AGGREGATE myaggn20a(BASETYPE = anyelement, SFUNC = tfp, + STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); + +-- create test data for polymorphic aggregates +create temp table t(f1 int, f2 int[], f3 text); +insert into t values(1,array[1],'a'); +insert into t values(1,array[11],'b'); +insert into t values(1,array[111],'c'); +insert into t values(2,array[2],'a'); +insert into t values(2,array[22],'b'); +insert into t values(2,array[222],'c'); +insert into t values(3,array[3],'a'); +insert into t values(3,array[3],'b'); + +-- test the successfully created polymorphic aggregates +select f3, myaggp01a(*) from t group by f3; +select f3, myaggp03a(*) from t group by f3; +select f3, myaggp03b(*) from t group by f3; +select f3, myaggp05a(f1) from t group by f3; +select f3, myaggp06a(f1) from t group by f3; +select f3, myaggp08a(f1) from t group by f3; +select f3, myaggp09a(f1) from t group by f3; +select f3, myaggp09b(f1) from t group by f3; +select f3, myaggp10a(f1) from t group by f3; +select f3, myaggp10b(f1) from t group by f3; +select f3, myaggp20a(f1) from t group by f3; +select f3, myaggp20b(f1) from t group by f3; +select f3, myaggn01a(*) from t group by f3; +select f3, myaggn01b(*) from t group by f3; +select f3, myaggn03a(*) from t group by f3; +select f3, myaggn05a(f1) from t group by f3; +select f3, myaggn05b(f1) from t group by f3; +select f3, myaggn06a(f1) from t group by f3; +select f3, myaggn06b(f1) from t group by f3; +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;