Thread: Re: [HACKERS] Missing array support
Tom Lane wrote: > If I were you I'd file this on the to-fix-later list and concentrate > on polymorphic aggregates during the next couple days. If that's not > done by Tuesday it will be a tough sell to put in during beta. Attached is a patch that implements polymorphic aggregates. Included in the patch, I changed SQL language functions so that they could be declared with and use polymorphic types. This was necessary to facilitate my testing, and I had wanted to implement that all along anyway (in fact I'd still like to allow PL/pgSQL to use polymorphic types, but I'll try to do that separately). The attached compiles cleanly and passes all regression tests. I've also attached a test script and its output that I used to verify appropriate behavior of CREATE AGGREGATE. The script attempts to cover all possible combinations of inputs and outputs (wrt polymorphic vs non-polymorphic). As far as I can see, the behaviors look correct. Not sure if it makes sense to do so, but I could either add the test script to an existing regression test, or create a new one with it if desired. If there are no objections, please apply. Thanks, Joe Index: src/backend/catalog/pg_aggregate.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/catalog/pg_aggregate.c,v retrieving revision 1.58 diff -c -r1.58 pg_aggregate.c *** src/backend/catalog/pg_aggregate.c 25 Jun 2003 21:30:25 -0000 1.58 --- src/backend/catalog/pg_aggregate.c 29 Jun 2003 19:17:47 -0000 *************** *** 50,59 **** Oid finalfn = InvalidOid; /* can be omitted */ Oid finaltype; Oid fnArgs[FUNC_MAX_ARGS]; ! int nargs; Oid procOid; TupleDesc tupDesc; int i; ObjectAddress myself, referenced; --- 50,65 ---- Oid finalfn = InvalidOid; /* can be omitted */ Oid finaltype; Oid fnArgs[FUNC_MAX_ARGS]; ! int nargs_transfn; ! int nargs_finalfn; Oid procOid; TupleDesc tupDesc; int i; + Oid rettype; + Oid *true_oid_array_transfn; + Oid *true_oid_array_finalfn; + bool retset; + FuncDetailCode fdresult; ObjectAddress myself, referenced; *************** *** 68,91 **** MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid)); fnArgs[0] = aggTransType; if (aggBaseType == ANYOID) ! nargs = 1; else { fnArgs[1] = aggBaseType; ! nargs = 2; } ! transfn = LookupFuncName(aggtransfnName, nargs, fnArgs); if (!OidIsValid(transfn)) ! func_error("AggregateCreate", aggtransfnName, nargs, fnArgs, NULL); tup = SearchSysCache(PROCOID, ObjectIdGetDatum(transfn), 0, 0, 0); if (!HeapTupleIsValid(tup)) ! func_error("AggregateCreate", aggtransfnName, nargs, 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 --- 74,137 ---- MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid)); fnArgs[0] = aggTransType; if (aggBaseType == ANYOID) ! nargs_transfn = 1; else { fnArgs[1] = aggBaseType; ! nargs_transfn = 2; } ! ! /* ! * 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(aggtransfnName, NIL, nargs_transfn, fnArgs, ! &transfn, &rettype, &retset, ! &true_oid_array_transfn); ! ! /* only valid case is a normal function */ ! if (fdresult != FUNCDETAIL_NORMAL) ! func_error("AggregateCreate", aggtransfnName, nargs_transfn, fnArgs, NULL); ! if (!OidIsValid(transfn)) ! func_error("AggregateCreate", aggtransfnName, nargs_transfn, fnArgs, NULL); ! ! /* ! * enforce consistency with ANYARRAY and ANYELEMENT argument ! * and return types, possibly modifying return type along the way ! */ ! rettype = enforce_generic_type_consistency(fnArgs, true_oid_array_transfn, ! nargs_transfn, rettype); ! ! /* ! * func_get_detail will find functions requiring argument type coercion, ! * but we aren't prepared to deal with that ! */ ! if (true_oid_array_transfn[0] != ANYARRAYOID && ! true_oid_array_transfn[0] != ANYELEMENTOID && ! !IsBinaryCoercible(fnArgs[0], true_oid_array_transfn[0])) ! func_error("AggregateCreate", aggtransfnName, nargs_transfn, fnArgs, NULL); ! ! if (nargs_transfn == 2 && ! true_oid_array_transfn[1] != ANYARRAYOID && ! true_oid_array_transfn[1] != ANYELEMENTOID && ! !IsBinaryCoercible(fnArgs[1], true_oid_array_transfn[1])) ! func_error("AggregateCreate", aggtransfnName, nargs_transfn, fnArgs, NULL); ! ! 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_transfn, fnArgs, NULL); proc = (Form_pg_proc) GETSTRUCT(tup); /* * If the transfn is strict and the initval is NULL, make sure input *************** *** 105,121 **** { 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); } else { --- 151,185 ---- { MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid)); fnArgs[0] = aggTransType; ! nargs_finalfn = 1; ! ! fdresult = func_get_detail(aggfinalfnName, NIL, 1, fnArgs, ! &finalfn, &rettype, &retset, ! &true_oid_array_finalfn); ! ! /* only valid case is a normal function */ ! if (fdresult != FUNCDETAIL_NORMAL) ! func_error("AggregateCreate", aggfinalfnName, 1, fnArgs, NULL); ! if (!OidIsValid(finalfn)) func_error("AggregateCreate", aggfinalfnName, 1, fnArgs, NULL); ! ! /* ! * enforce consistency with ANYARRAY and ANYELEMENT argument ! * and return types, possibly modifying return type along the way ! */ ! finaltype = enforce_generic_type_consistency(fnArgs, ! true_oid_array_finalfn, ! nargs_finalfn, rettype); ! ! /* ! * func_get_detail will find functions requiring argument type coercion, ! * but we aren't prepared to deal with that ! */ ! if (true_oid_array_finalfn[0] != ANYARRAYOID && ! true_oid_array_finalfn[0] != ANYELEMENTOID && ! !IsBinaryCoercible(fnArgs[0], true_oid_array_finalfn[0])) func_error("AggregateCreate", aggfinalfnName, 1, fnArgs, NULL); } else { *************** *** 125,130 **** --- 189,222 ---- finaltype = aggTransType; } Assert(OidIsValid(finaltype)); + + /* + * special disallowed cases: + * 1) if finaltype (i.e. aggregate return type) is polymorphic, + * basetype must be polymorphic also + * 2) if finaltype (i.e. aggregate return type) is non-polymorphic, + * and transition function's second argument is non-polymorphic, then + * the transition function's first argument may not be polymorphic + * unless the state type is non-polymorphic + */ + if ((finaltype == ANYARRAYOID || + finaltype == ANYELEMENTOID) && + (aggBaseType != ANYARRAYOID && + aggBaseType != ANYELEMENTOID)) + elog(ERROR, "an aggregate returning ANYARRAY or ANYELEMENT " \ + "must also have either of the them as its base type"); + + + if ((finaltype != ANYARRAYOID && + finaltype != ANYELEMENTOID) && /* rt non-poly */ + (true_oid_array_transfn[0] == ANYARRAYOID || + true_oid_array_transfn[0] == ANYELEMENTOID) && /* tf arg1 poly */ + (true_oid_array_transfn[1] != ANYARRAYOID && + true_oid_array_transfn[1] != ANYELEMENTOID) && /* tf arg2 non-poly */ + (aggTransType == ANYARRAYOID || + aggTransType == ANYELEMENTOID)) /* st arg1 poly */ + elog(ERROR, "the state function's first argument is ambiguous in a " \ + "context that cannot support it"); /* * Everything looks okay. Try to create the pg_proc entry for the Index: src/backend/catalog/pg_proc.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/catalog/pg_proc.c,v retrieving revision 1.97 diff -c -r1.97 pg_proc.c *** src/backend/catalog/pg_proc.c 15 Jun 2003 17:59:10 -0000 1.97 --- src/backend/catalog/pg_proc.c 29 Jun 2003 16:26:36 -0000 *************** *** 377,383 **** typerelid = typeidTypeRelid(rettype); ! if (fn_typtype == 'b' || fn_typtype == 'd') { /* Shouldn't have a typerelid */ Assert(typerelid == InvalidOid); --- 377,386 ---- typerelid = typeidTypeRelid(rettype); ! if (fn_typtype == 'b' || ! fn_typtype == 'd' || ! (fn_typtype == 'p' && rettype == ANYARRAYOID) || ! (fn_typtype == 'p' && rettype == ANYELEMENTOID)) { /* Shouldn't have a typerelid */ Assert(typerelid == InvalidOid); *************** *** 595,610 **** functyptype = get_typtype(proc->prorettype); /* Disallow pseudotypes in arguments and result */ ! /* except that return type can be RECORD or VOID */ if (functyptype == 'p' && proc->prorettype != RECORDOID && ! proc->prorettype != VOIDOID) elog(ERROR, "SQL functions cannot return type %s", format_type_be(proc->prorettype)); for (i = 0; i < proc->pronargs; i++) { ! if (get_typtype(proc->proargtypes[i]) == 'p') elog(ERROR, "SQL functions cannot have arguments of type %s", format_type_be(proc->proargtypes[i])); } --- 598,617 ---- functyptype = get_typtype(proc->prorettype); /* Disallow pseudotypes in arguments and result */ ! /* except that return type can be RECORD, VOID, ANYARRAY, or ANYELEMENT */ if (functyptype == 'p' && proc->prorettype != RECORDOID && ! proc->prorettype != VOIDOID && ! proc->prorettype != ANYARRAYOID && ! proc->prorettype != ANYELEMENTOID) elog(ERROR, "SQL functions cannot return type %s", format_type_be(proc->prorettype)); for (i = 0; i < proc->pronargs; i++) { ! if (get_typtype(proc->proargtypes[i]) == 'p' && ! proc->proargtypes[i] != ANYARRAYOID && ! proc->proargtypes[i] != ANYELEMENTOID) elog(ERROR, "SQL functions cannot have arguments of type %s", format_type_be(proc->proargtypes[i])); } Index: src/backend/commands/aggregatecmds.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/commands/aggregatecmds.c,v retrieving revision 1.8 diff -c -r1.8 aggregatecmds.c *** src/backend/commands/aggregatecmds.c 27 Jun 2003 14:45:27 -0000 1.8 --- src/backend/commands/aggregatecmds.c 29 Jun 2003 16:26:36 -0000 *************** *** 120,126 **** baseTypeId = typenameTypeId(baseType); transTypeId = typenameTypeId(transType); ! if (get_typtype(transTypeId) == 'p') elog(ERROR, "Aggregate transition datatype cannot be %s", format_type_be(transTypeId)); --- 120,128 ---- baseTypeId = typenameTypeId(baseType); transTypeId = typenameTypeId(transType); ! if (get_typtype(transTypeId) == 'p' && ! transTypeId != ANYARRAYOID && ! transTypeId != ANYELEMENTOID) elog(ERROR, "Aggregate transition datatype cannot be %s", format_type_be(transTypeId)); Index: src/backend/executor/functions.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/executor/functions.c,v retrieving revision 1.66 diff -c -r1.66 functions.c *** src/backend/executor/functions.c 12 Jun 2003 17:29:26 -0000 1.66 --- src/backend/executor/functions.c 29 Jun 2003 16:26:36 -0000 *************** *** 20,25 **** --- 20,26 ---- #include "executor/execdefs.h" #include "executor/executor.h" #include "executor/functions.h" + #include "parser/parse_expr.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "tcop/utility.h" *************** *** 212,221 **** if (nargs > 0) { argOidVect = (Oid *) palloc(nargs * sizeof(Oid)); ! memcpy(argOidVect, ! procedureStruct->proargtypes, ! nargs * sizeof(Oid)); } else argOidVect = (Oid *) NULL; --- 213,235 ---- if (nargs > 0) { + List *p; + int argnum = 0; + argOidVect = (Oid *) palloc(nargs * sizeof(Oid)); ! if (finfo->fn_expr) ! { ! /* ! * If we have a function expression node available to us ! * use it, as any polymorphic types should have been ! * disambiguated for us already ! */ ! foreach(p, ((FuncExpr *) finfo->fn_expr)->args) ! argOidVect[argnum++] = exprType((Node *) lfirst(p)); ! } ! else ! memcpy(argOidVect, procedureStruct->proargtypes, ! nargs * sizeof(Oid)); } else argOidVect = (Oid *) NULL; Index: src/backend/executor/nodeAgg.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/executor/nodeAgg.c,v retrieving revision 1.109 diff -c -r1.109 nodeAgg.c *** src/backend/executor/nodeAgg.c 25 Jun 2003 21:30:28 -0000 1.109 --- src/backend/executor/nodeAgg.c 29 Jun 2003 16:26:36 -0000 *************** *** 59,64 **** --- 59,65 ---- #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" *************** *** 1187,1193 **** --- 1188,1199 ---- AclResult aclresult; Oid transfn_oid, finalfn_oid; + FuncExpr *transfnexpr, + *finalfnexpr; Datum textInitVal; + List *fargs; + Oid agg_rt_basetype; + Oid transfn_arg1_type; int i; /* Planner should have assigned aggregate to correct level */ *************** *** 1238,1243 **** --- 1244,1274 ---- &peraggstate->transtypeLen, &peraggstate->transtypeByVal); + peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn; + peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn; + + /* get the runtime aggregate argument type */ + fargs = aggref->args; + agg_rt_basetype = exprType((Node *) nth(0, fargs)); + + expand_aggregate(agg_rt_basetype, + aggform->aggtranstype, + aggref->aggfnoid, + transfn_oid, + finalfn_oid, + &transfnexpr, + &finalfnexpr, + &transfn_arg1_type); + + 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; + } + /* * initval is potentially null, so don't try to access it as a * struct field. Must do it the hard way with SysCacheGetAttr. *************** *** 1250,1263 **** 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); /* * If the transfn is strict and the initval is NULL, make sure --- 1281,1287 ---- peraggstate->initValue = (Datum) 0; else peraggstate->initValue = GetAggInitVal(textInitVal, ! transfn_arg1_type); /* * If the transfn is strict and the initval is NULL, make sure Index: src/backend/nodes/copyfuncs.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/nodes/copyfuncs.c,v retrieving revision 1.258 diff -c -r1.258 copyfuncs.c *** src/backend/nodes/copyfuncs.c 29 Jun 2003 00:33:43 -0000 1.258 --- src/backend/nodes/copyfuncs.c 29 Jun 2003 16:26:36 -0000 *************** *** 728,733 **** --- 728,734 ---- COPY_SCALAR_FIELD(agglevelsup); COPY_SCALAR_FIELD(aggstar); COPY_SCALAR_FIELD(aggdistinct); + COPY_NODE_FIELD(args); return newnode; } Index: src/backend/nodes/equalfuncs.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/nodes/equalfuncs.c,v retrieving revision 1.201 diff -c -r1.201 equalfuncs.c *** src/backend/nodes/equalfuncs.c 29 Jun 2003 00:33:43 -0000 1.201 --- src/backend/nodes/equalfuncs.c 29 Jun 2003 16:26:36 -0000 *************** *** 205,210 **** --- 205,211 ---- COMPARE_SCALAR_FIELD(agglevelsup); COMPARE_SCALAR_FIELD(aggstar); COMPARE_SCALAR_FIELD(aggdistinct); + COMPARE_NODE_FIELD(args); return true; } Index: src/backend/nodes/outfuncs.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/nodes/outfuncs.c,v retrieving revision 1.211 diff -c -r1.211 outfuncs.c *** src/backend/nodes/outfuncs.c 29 Jun 2003 00:33:43 -0000 1.211 --- src/backend/nodes/outfuncs.c 29 Jun 2003 16:26:36 -0000 *************** *** 616,621 **** --- 616,622 ---- WRITE_UINT_FIELD(agglevelsup); WRITE_BOOL_FIELD(aggstar); WRITE_BOOL_FIELD(aggdistinct); + WRITE_NODE_FIELD(args); } static void Index: src/backend/nodes/readfuncs.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/nodes/readfuncs.c,v retrieving revision 1.157 diff -c -r1.157 readfuncs.c *** src/backend/nodes/readfuncs.c 29 Jun 2003 00:33:43 -0000 1.157 --- src/backend/nodes/readfuncs.c 29 Jun 2003 16:26:36 -0000 *************** *** 416,421 **** --- 416,422 ---- READ_UINT_FIELD(agglevelsup); READ_BOOL_FIELD(aggstar); READ_BOOL_FIELD(aggdistinct); + READ_NODE_FIELD(args); READ_DONE(); } Index: src/backend/optimizer/util/clauses.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/optimizer/util/clauses.c,v retrieving revision 1.142 diff -c -r1.142 clauses.c *** src/backend/optimizer/util/clauses.c 29 Jun 2003 00:33:43 -0000 1.142 --- src/backend/optimizer/util/clauses.c 29 Jun 2003 16:26:36 -0000 *************** *** 133,138 **** --- 133,160 ---- } /***************************************************************************** + * FUNCTION clause functions + *****************************************************************************/ + + /* + * make_funcclause + * Creates a function clause given its function info and argument list. + */ + Expr * + make_funcclause(Oid funcid, Oid funcresulttype, bool funcretset, + CoercionForm funcformat, List *funcargs) + { + FuncExpr *expr = makeNode(FuncExpr); + + expr->funcid = funcid; + expr->funcresulttype = funcresulttype; + expr->funcretset = funcretset; + expr->funcformat = funcformat; + expr->args = funcargs; + return (Expr *) expr; + } + + /***************************************************************************** * NOT clause functions *****************************************************************************/ Index: src/backend/parser/parse_agg.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/parser/parse_agg.c,v retrieving revision 1.53 diff -c -r1.53 parse_agg.c *** src/backend/parser/parse_agg.c 6 Jun 2003 15:04:02 -0000 1.53 --- src/backend/parser/parse_agg.c 29 Jun 2003 16:26:36 -0000 *************** *** 14,25 **** --- 14,29 ---- */ #include "postgres.h" + #include "catalog/pg_type.h" + #include "nodes/params.h" #include "optimizer/clauses.h" #include "optimizer/tlist.h" #include "optimizer/var.h" #include "parser/parse_agg.h" + #include "parser/parse_type.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" + #include "utils/lsyscache.h" typedef struct *************** *** 312,314 **** --- 316,539 ---- return expression_tree_walker(node, check_ungrouped_columns_walker, (void *) context); } + + /* + * Create function expressions for the transition and final functions + * of an aggregate so that they can be attached to the FmgrInfo nodes + * of AggStatePerAgg. If we didn't, the functions would not be able to + * know what argument and return data types to use for any that are + * polymorphic in its definition. + */ + void + expand_aggregate(Oid agg_rt_basetype, + Oid agg_statetype, + Oid agg_fnoid, + Oid transfn_oid, + Oid finalfn_oid, + FuncExpr **transfnexpr, + FuncExpr **finalfnexpr, + Oid *transfn_arg1_type) + { + Oid *transfn_arg_types; + List *transfn_args = NIL; + int transfn_nargs; + Oid transfn_ret_type; + Oid *finalfn_arg_types = NULL; + List *finalfn_args = NIL; + Oid finalfn_ret_type = InvalidOid; + int finalfn_nargs = 0; + Param *arg0; + Param *arg1; + + /* get the transition function argument and return types */ + transfn_ret_type = get_func_rettype(transfn_oid); + transfn_arg_types = get_func_argtypes(transfn_oid, &transfn_nargs); + + /* resolve any polymorphic types */ + if (transfn_nargs == 2) + { + /* base type was not ANY */ + if ((transfn_arg_types[0] == ANYARRAYOID || + transfn_arg_types[0] == ANYELEMENTOID) && + (transfn_arg_types[1] == ANYARRAYOID || + transfn_arg_types[1] == ANYELEMENTOID)) + { + /* + * If both transfn args are polymorphic, we can + * resolve transfn arg 1 using base type as context + */ + transfn_arg_types[0] = resolve_type(transfn_arg_types[0], + agg_rt_basetype); + } + else if ((transfn_arg_types[0] == ANYARRAYOID || + transfn_arg_types[0] == ANYELEMENTOID)) + { + /* + * Otherwise, if transfn arg 1 is polymorphic, we can + * resolve it using state type as context. This is only + * safe because we prevented the situation where both + * state type and transfn arg 1 are polymorphic with a + * non-polymorphic transfn arg 2, during aggregate creation. + */ + transfn_arg_types[0] = resolve_type(transfn_arg_types[0], + agg_statetype); + } + + /* + * Now, if transfn arg 2 is polymorphic, we can set it to the runtime + * base type without further adieu + */ + if (transfn_arg_types[1] == ANYARRAYOID || + transfn_arg_types[1] == ANYELEMENTOID) + transfn_arg_types[1] = agg_rt_basetype; + + /* + * Build arg list to use on the transfn FuncExpr node. We really + * only care that transfn can discover the actual argument types + * at runtime using get_fn_expr_argtype() + */ + arg0 = makeNode(Param); + arg0->paramkind = PARAM_EXEC; + arg0->paramid = -1; + arg0->paramtype = transfn_arg_types[0]; + + arg1 = makeNode(Param); + arg1->paramkind = PARAM_EXEC; + arg1->paramid = -1; + arg1->paramtype = transfn_arg_types[1]; + + transfn_args = makeList2(arg0, arg1); + + /* + * the state transition function always returns the same type + * as its first argument + */ + if (transfn_ret_type == ANYARRAYOID || + transfn_ret_type == ANYELEMENTOID) + transfn_ret_type = transfn_arg_types[0]; + } + else if (transfn_nargs == 1) + /* + * base type was ANY, therefore the aggregate return type should + * be non-polymorphic + */ + { + Oid finaltype = get_func_rettype(agg_fnoid); + + /* + * this should have been prevented in AggregateCreate, + * but check anyway + */ + if (finaltype == ANYARRAYOID || finaltype == ANYELEMENTOID) + elog(ERROR, "an aggregate returning ANYARRAY or ANYELEMENT " \ + "must also have either of the them as its base type"); + + /* see if we have a final function */ + if (OidIsValid(finalfn_oid)) + { + finalfn_arg_types = get_func_argtypes(finalfn_oid, &finalfn_nargs); + if (finalfn_nargs != 1) + elog(ERROR, "final function takes unexpected number " \ + "of arguments: %d", finalfn_nargs); + + /* + * final function argument is always the same as the state + * function return type + */ + if (finalfn_arg_types[0] != ANYARRAYOID && + finalfn_arg_types[0] != ANYELEMENTOID) + { + /* if it is not ambiguous, use it */ + transfn_ret_type = finalfn_arg_types[0]; + } + else + { + /* if it is ambiguous, try to derive it */ + finalfn_ret_type = finaltype; + finalfn_arg_types[0] = resolve_type(finalfn_arg_types[0], + finalfn_ret_type); + transfn_ret_type = finalfn_arg_types[0]; + } + } + else + transfn_ret_type = finaltype; + + transfn_arg_types[0] = resolve_type(transfn_arg_types[0], + transfn_ret_type); + + /* + * Build arg list to use on the transfn FuncExpr node. We really + * only care that transfn can discover the actual argument types + * at runtime using get_fn_expr_argtype() + */ + arg0 = makeNode(Param); + arg0->paramkind = PARAM_EXEC; + arg0->paramid = -1; + arg0->paramtype = transfn_arg_types[0]; + + transfn_args = makeList1(arg0); + } + else + elog(ERROR, "state transition function takes unexpected number " \ + "of arguments: %d", transfn_nargs); + + if (OidIsValid(finalfn_oid)) + { + /* get the final function argument and return types */ + if (finalfn_ret_type == InvalidOid) + finalfn_ret_type = get_func_rettype(finalfn_oid); + + if (!finalfn_arg_types) + { + finalfn_arg_types = get_func_argtypes(finalfn_oid, &finalfn_nargs); + if (finalfn_nargs != 1) + elog(ERROR, "final function takes unexpected number " \ + "of arguments: %d", finalfn_nargs); + } + + /* + * final function argument is always the same as the state + * function return type, which by now should have been resolved + */ + if (finalfn_arg_types[0] == ANYARRAYOID || + finalfn_arg_types[0] == ANYELEMENTOID) + finalfn_arg_types[0] = transfn_ret_type; + + /* + * Build arg list to use on the finalfn FuncExpr node. We really + * only care that finalfn can discover the actual argument types + * at runtime using get_fn_expr_argtype() + */ + arg0 = makeNode(Param); + arg0->paramkind = PARAM_EXEC; + arg0->paramid = -1; + arg0->paramtype = finalfn_arg_types[0]; + + finalfn_args = makeList1(arg0); + + finalfn_ret_type = resolve_type(finalfn_ret_type, + finalfn_arg_types[0]); + } + + *transfnexpr = (FuncExpr *) make_funcclause(transfn_oid, + transfn_ret_type, + false, + COERCE_DONTCARE, + transfn_args); + + if (OidIsValid(finalfn_oid)) + { + *finalfnexpr = (FuncExpr *) make_funcclause(finalfn_oid, + finalfn_ret_type, + false, + COERCE_DONTCARE, + finalfn_args); + } + + /* + * we need to return the resolved transfn arg1 type to be used + * by GetAggInitVal + */ + *transfn_arg1_type = transfn_arg_types[0]; + } + Index: src/backend/parser/parse_coerce.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/parser/parse_coerce.c,v retrieving revision 2.101 diff -c -r2.101 parse_coerce.c *** src/backend/parser/parse_coerce.c 27 Jun 2003 00:33:25 -0000 2.101 --- src/backend/parser/parse_coerce.c 29 Jun 2003 16:26:36 -0000 *************** *** 859,865 **** /* Get the element type based on the array type, if we have one */ if (OidIsValid(array_typeid)) { ! array_typelem = get_element_type(array_typeid); if (!OidIsValid(array_typelem)) elog(ERROR, "Argument declared ANYARRAY is not an array: %s", format_type_be(array_typeid)); --- 859,869 ---- /* Get the element type based on the array type, if we have one */ if (OidIsValid(array_typeid)) { ! if (array_typeid != ANYARRAYOID) ! array_typelem = get_element_type(array_typeid); ! else ! array_typelem = ANYELEMENTOID; ! if (!OidIsValid(array_typelem)) elog(ERROR, "Argument declared ANYARRAY is not an array: %s", format_type_be(array_typeid)); *************** *** 919,925 **** { if (!OidIsValid(array_typeid)) { ! array_typeid = get_array_type(elem_typeid); if (!OidIsValid(array_typeid)) elog(ERROR, "Cannot find array type for datatype %s", format_type_be(elem_typeid)); --- 923,933 ---- { if (!OidIsValid(array_typeid)) { ! if (elem_typeid != ANYELEMENTOID) ! array_typeid = get_array_type(elem_typeid); ! else ! array_typeid = ANYARRAYOID; ! if (!OidIsValid(array_typeid)) elog(ERROR, "Cannot find array type for datatype %s", format_type_be(elem_typeid)); Index: src/backend/parser/parse_func.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/parser/parse_func.c,v retrieving revision 1.152 diff -c -r1.152 parse_func.c *** src/backend/parser/parse_func.c 25 Jun 2003 21:30:31 -0000 1.152 --- src/backend/parser/parse_func.c 29 Jun 2003 16:26:36 -0000 *************** *** 336,341 **** --- 336,342 ---- aggref->target = lfirst(fargs); aggref->aggstar = agg_star; aggref->aggdistinct = agg_distinct; + aggref->args = fargs; /* parse_agg.c does additional aggregate-specific processing */ transformAggregateCall(pstate, aggref); Index: src/backend/parser/parse_type.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/parser/parse_type.c,v retrieving revision 1.57 diff -c -r1.57 parse_type.c *** src/backend/parser/parse_type.c 29 Apr 2003 22:13:10 -0000 1.57 --- src/backend/parser/parse_type.c 29 Jun 2003 16:26:36 -0000 *************** *** 484,486 **** --- 484,536 ---- pfree(buf.data); } + + /* + * Given a type_to_resolve oid, typically defined at function creation + * (e.g. a function argument or return type), and context_type oid, + * typically gleaned by the parser as one of the actual arguments + * at function call time, derive the runtime type of type_to_resolve. + * The intent is to use runtime context to determine what type we should + * assign to a polymorphic argument or return type. + * + * The rules for this resolution are as follows: + * 1) if the context type is polymorphic, punt and return type_to_resolve + * unchanged + * 2) if type_to_resolve is ANYARRAY (polymorphic), then return context_type + * if it is already an array type, or get its array type if not + * 3) if type_to_resolve is ANYELEMENT (polymorphic), then return context_type + * if it is already an elemental type, or get its element type if not + * 4) if type_to_resolve is non-polymorphic, return it unchanged + */ + Oid + resolve_type(Oid type_to_resolve, Oid context_type) + { + Oid resolved_type; + + if (context_type == ANYARRAYOID || context_type == ANYELEMENTOID) + resolved_type = type_to_resolve; + else if (type_to_resolve == ANYARRAYOID) + /* any array */ + { + Oid context_type_arraytype = get_array_type(context_type); + + if (context_type_arraytype != InvalidOid) + resolved_type = context_type_arraytype; + else + resolved_type = context_type; + } + else if (type_to_resolve == ANYELEMENTOID) + /* any element */ + { + Oid context_type_elemtype = get_element_type(context_type); + + if (context_type_elemtype != InvalidOid) + resolved_type = context_type_elemtype; + else + resolved_type = context_type; + } + else + resolved_type = type_to_resolve; + + return resolved_type; + } Index: src/backend/utils/cache/lsyscache.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/utils/cache/lsyscache.c,v retrieving revision 1.100 diff -c -r1.100 lsyscache.c *** src/backend/utils/cache/lsyscache.c 27 Jun 2003 00:33:25 -0000 1.100 --- src/backend/utils/cache/lsyscache.c 29 Jun 2003 16:26:36 -0000 *************** *** 719,724 **** --- 719,758 ---- } /* + * get_func_argtypes + * Given procedure id, return the function's argument types. + * Also pass back the number of arguments. + */ + Oid * + get_func_argtypes(Oid funcid, int *nargs) + { + HeapTuple tp; + Form_pg_proc procstruct; + Oid *result = NULL; + int i; + + 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); + *nargs = (int) procstruct->pronargs; + + if (*nargs > 0) + { + result = (Oid *) palloc(*nargs * sizeof(Oid)); + + for (i = 0; i < *nargs; i++) + result[i] = procstruct->proargtypes[i]; + } + + ReleaseSysCache(tp); + return result; + } + + /* * get_func_retset * Given procedure id, return the function's proretset flag. */ Index: src/include/nodes/primnodes.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/nodes/primnodes.h,v retrieving revision 1.86 diff -c -r1.86 primnodes.h *** src/include/nodes/primnodes.h 29 Jun 2003 00:33:44 -0000 1.86 --- src/include/nodes/primnodes.h 29 Jun 2003 16:26:36 -0000 *************** *** 226,231 **** --- 226,232 ---- Index agglevelsup; /* > 0 if agg belongs to outer query */ bool aggstar; /* TRUE if argument was really '*' */ bool aggdistinct; /* TRUE if it's agg(DISTINCT ...) */ + List *args; /* arguments to the aggregate */ } Aggref; /* ---------------- Index: src/include/optimizer/clauses.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/optimizer/clauses.h,v retrieving revision 1.65 diff -c -r1.65 clauses.h *** src/include/optimizer/clauses.h 25 Jun 2003 21:30:33 -0000 1.65 --- src/include/optimizer/clauses.h 29 Jun 2003 16:26:36 -0000 *************** *** 28,33 **** --- 28,36 ---- extern Node *get_leftop(Expr *clause); extern Node *get_rightop(Expr *clause); + extern Expr *make_funcclause(Oid funcid, Oid funcresulttype, bool funcretset, + CoercionForm funcformat, List *funcargs); + extern bool not_clause(Node *clause); extern Expr *make_notclause(Expr *notclause); extern Expr *get_notclausearg(Expr *notclause); Index: src/include/parser/parse_agg.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/parser/parse_agg.h,v retrieving revision 1.26 diff -c -r1.26 parse_agg.h *** src/include/parser/parse_agg.h 6 Jun 2003 15:04:03 -0000 1.26 --- src/include/parser/parse_agg.h 29 Jun 2003 16:26:36 -0000 *************** *** 18,22 **** --- 18,30 ---- extern void transformAggregateCall(ParseState *pstate, Aggref *agg); extern void parseCheckAggregates(ParseState *pstate, Query *qry); + extern void expand_aggregate(Oid agg_rt_basetype, + Oid agg_statetype, + Oid agg_fnoid, + Oid transfn_oid, + Oid finalfn_oid, + FuncExpr **transfnexpr, + FuncExpr **finalfnexpr, + Oid *transfn_arg1_type); #endif /* PARSE_AGG_H */ Index: src/include/parser/parse_type.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/parser/parse_type.h,v retrieving revision 1.24 diff -c -r1.24 parse_type.h *** src/include/parser/parse_type.h 31 Aug 2002 22:10:47 -0000 1.24 --- src/include/parser/parse_type.h 29 Jun 2003 16:26:36 -0000 *************** *** 40,45 **** --- 40,46 ---- extern Oid typeidTypeRelid(Oid type_id); extern void parseTypeString(const char *str, Oid *type_id, int32 *typmod); + extern Oid resolve_type(Oid type_to_resolve, Oid context_type); #define ISCOMPLEX(typeid) (typeidTypeRelid(typeid) != InvalidOid) Index: src/include/utils/lsyscache.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/utils/lsyscache.h,v retrieving revision 1.75 diff -c -r1.75 lsyscache.h *** src/include/utils/lsyscache.h 27 Jun 2003 00:33:26 -0000 1.75 --- src/include/utils/lsyscache.h 29 Jun 2003 16:26:36 -0000 *************** *** 50,55 **** --- 50,56 ---- extern RegProcedure get_oprjoin(Oid opno); extern char *get_func_name(Oid funcid); extern Oid get_func_rettype(Oid funcid); + extern Oid *get_func_argtypes(Oid funcid, int *nargs); extern bool get_func_retset(Oid funcid); extern bool func_strict(Oid funcid); extern char func_volatile(Oid funcid); -- 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 OR REPLACE FUNCTION stfp(anyarray) returns anyarray as 'select $1' language 'sql'; -- non-polymorphic single arg transfn CREATE OR REPLACE FUNCTION stfnp(int[]) returns int[] as 'select $1' language 'sql'; -- dual polymorphic transfn CREATE OR REPLACE FUNCTION tfp(anyarray,anyelement) returns anyarray as 'select $1 || $2' language 'sql'; -- dual non-polymorphic transfn CREATE OR REPLACE FUNCTION tfnp(int[],int) returns int[] as 'select $1 || $2' language 'sql'; -- arg1 only polymorphic transfn CREATE OR REPLACE FUNCTION tf1p(anyarray,int) returns anyarray as 'select $1' language 'sql'; -- arg2 only polymorphic transfn CREATE OR REPLACE FUNCTION tf2p(int[],anyelement) returns int[] as 'select $1' language 'sql'; -- finalfn polymorphic CREATE OR REPLACE FUNCTION ffp(anyarray) returns anyarray as 'select $1' language 'sql'; -- finalfn non-polymorphic CREATE OR REPLACE 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 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; -- 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 OR REPLACE FUNCTION stfp(anyarray) returns anyarray as 'select $1' language 'sql'; CREATE FUNCTION -- non-polymorphic single arg transfn CREATE OR REPLACE FUNCTION stfnp(int[]) returns int[] as 'select $1' language 'sql'; CREATE FUNCTION -- dual polymorphic transfn CREATE OR REPLACE FUNCTION tfp(anyarray,anyelement) returns anyarray as 'select $1 || $2' language 'sql'; CREATE FUNCTION -- dual non-polymorphic transfn CREATE OR REPLACE FUNCTION tfnp(int[],int) returns int[] as 'select $1 || $2' language 'sql'; CREATE FUNCTION -- arg1 only polymorphic transfn CREATE OR REPLACE FUNCTION tf1p(anyarray,int) returns anyarray as 'select $1' language 'sql'; CREATE FUNCTION -- arg2 only polymorphic transfn CREATE OR REPLACE FUNCTION tf2p(int[],anyelement) returns int[] as 'select $1' language 'sql'; CREATE FUNCTION -- finalfn polymorphic CREATE OR REPLACE FUNCTION ffp(anyarray) returns anyarray as 'select $1' language 'sql'; CREATE FUNCTION -- finalfn non-polymorphic CREATE OR REPLACE FUNCTION ffnp(int[]) returns int[] as 'select $1' language 'sql'; CREATE FUNCTION -- 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 = '{}'); CREATE AGGREGATE -- P N -- should ERROR: stfnp(anyarray) not matched by stfnp(int[]) CREATE AGGREGATE myaggp02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: AggregateCreate: function stfnp(anyarray) does not exist -- N P -- should CREATE CREATE AGGREGATE myaggp03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], FINALFUNC = ffp, INITCOND = '{}'); CREATE AGGREGATE CREATE AGGREGATE myaggp03b(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], INITCOND = '{}'); CREATE AGGREGATE -- 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 returning ANYARRAY or ANYELEMENT must also have either of the them as its base type CREATE AGGREGATE myaggp04b(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, INITCOND = '{}'); ERROR: an aggregate returning ANYARRAY or ANYELEMENT must also have either of the 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 = '{}'); CREATE AGGREGATE -- N N N P -- should CREATE CREATE AGGREGATE myaggp06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[], FINALFUNC = ffp, INITCOND = '{}'); CREATE AGGREGATE -- 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 = '{}'); CREATE AGGREGATE -- N P N N -- should CREATE CREATE AGGREGATE myaggp09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[], FINALFUNC = ffp, INITCOND = '{}'); CREATE AGGREGATE CREATE AGGREGATE myaggp09b(BASETYPE = int, SFUNC = tf1p, STYPE = int[], INITCOND = '{}'); CREATE AGGREGATE -- N P N P -- should CREATE CREATE AGGREGATE myaggp10a(BASETYPE = int, SFUNC = tfp, STYPE = int[], FINALFUNC = ffp, INITCOND = '{}'); CREATE AGGREGATE CREATE AGGREGATE myaggp10b(BASETYPE = int, SFUNC = tfp, STYPE = int[], INITCOND = '{}'); CREATE AGGREGATE -- 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: AggregateCreate: function tfnp(anyarray, integer) does not exist -- 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: AggregateCreate: function tf2p(anyarray, integer) does not exist -- 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 returning ANYARRAY or ANYELEMENT must also have either of the them as its base type CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, INITCOND = '{}'); ERROR: an aggregate returning ANYARRAY or ANYELEMENT must also have either of the 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: AggregateCreate: function tfp(anyarray, integer) does not exist CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, INITCOND = '{}'); ERROR: AggregateCreate: function tfp(anyarray, integer) does not exist -- 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 CREATE AGGREGATE myaggp20b(BASETYPE = anyelement, SFUNC = tfp, STYPE = anyarray, INITCOND = '{}'); CREATE AGGREGATE -- 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 CREATE AGGREGATE myaggn01b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = int4[], INITCOND = '{}'); CREATE AGGREGATE -- P N -- should ERROR: stfnp(anyarray) not matched by stfnp(int[]) CREATE AGGREGATE myaggn02a(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: AggregateCreate: function stfnp(anyarray) does not exist CREATE AGGREGATE myaggn02b(BASETYPE = "ANY", SFUNC = stfnp, STYPE = anyarray, INITCOND = '{}'); ERROR: AggregateCreate: function stfnp(anyarray) does not exist -- N P -- should CREATE CREATE AGGREGATE myaggn03a(BASETYPE = "ANY", SFUNC = stfp, STYPE = int4[], FINALFUNC = ffnp, INITCOND = '{}'); CREATE AGGREGATE -- P P -- should ERROR: ffnp(anyarray) not matched by ffnp(int[]) CREATE AGGREGATE myaggn04a(BASETYPE = "ANY", SFUNC = stfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: AggregateCreate: function ffnp(anyarray) does not exist -- 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 CREATE AGGREGATE myaggn05b(BASETYPE = int, SFUNC = tfnp, STYPE = int[], INITCOND = '{}'); CREATE AGGREGATE -- N N N P -- should CREATE CREATE AGGREGATE myaggn06a(BASETYPE = int, SFUNC = tf2p, STYPE = int[], FINALFUNC = ffnp, INITCOND = '{}'); CREATE AGGREGATE CREATE AGGREGATE myaggn06b(BASETYPE = int, SFUNC = tf2p, STYPE = int[], INITCOND = '{}'); CREATE AGGREGATE -- 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 CREATE AGGREGATE myaggn08b(BASETYPE = anyelement, SFUNC = tf2p, STYPE = int[], INITCOND = '{}'); CREATE AGGREGATE -- N P N N -- should CREATE CREATE AGGREGATE myaggn09a(BASETYPE = int, SFUNC = tf1p, STYPE = int[], FINALFUNC = ffnp, INITCOND = '{}'); CREATE AGGREGATE -- N P N P -- should CREATE CREATE AGGREGATE myaggn10a(BASETYPE = int, SFUNC = tfp, STYPE = int[], FINALFUNC = ffnp, INITCOND = '{}'); CREATE AGGREGATE -- 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: AggregateCreate: function tfnp(anyarray, integer) does not exist CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, INITCOND = '{}'); ERROR: AggregateCreate: function tfnp(anyarray, integer) does not exist -- 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: AggregateCreate: function tf2p(anyarray, integer) does not exist CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, INITCOND = '{}'); ERROR: AggregateCreate: function tf2p(anyarray, integer) does not exist -- 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: AggregateCreate: function ffnp(anyarray) does not exist -- 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: AggregateCreate: function tfp(anyarray, integer) does not exist -- 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 table t(f1 int, f2 int[], f3 text); CREATE TABLE insert into t values(1,array[1],'a'); INSERT 1253618 1 insert into t values(1,array[11],'b'); INSERT 1253619 1 insert into t values(1,array[111],'c'); INSERT 1253620 1 insert into t values(2,array[2],'a'); INSERT 1253621 1 insert into t values(2,array[22],'b'); INSERT 1253622 1 insert into t values(2,array[222],'c'); INSERT 1253623 1 insert into t values(3,array[3],'a'); INSERT 1253624 1 insert into t values(3,array[3],'b'); INSERT 1253625 1 -- 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)
Joe Conway <mail@joeconway.com> writes: > Included in the patch, I changed SQL language functions so that they > could be declared with and use polymorphic types. I'm not convinced that will work ... in particular, does the parsetree get fixed correctly when a SQL function is inlined? regards, tom lane
Tom Lane wrote: > Joe Conway <mail@joeconway.com> writes: >>Included in the patch, I changed SQL language functions so that they >>could be declared with and use polymorphic types. > > I'm not convinced that will work ... in particular, does the parsetree > get fixed correctly when a SQL function is inlined? > I'll try it out. What's the easiest way to be sure the function get's inlined? In any case, it's easy enough to rip that part out of the patch -- it just would have been a lot more painful to test without it. Joe
Tom Lane wrote: > Joe Conway <mail@joeconway.com> writes: > >>Included in the patch, I changed SQL language functions so that they >>could be declared with and use polymorphic types. > > I'm not convinced that will work ... in particular, does the parsetree > get fixed correctly when a SQL function is inlined? As far as I can see (and verified through testing), evaluate_function() is no problem; it passes on the args from the original FuncExpr at about line 1690 of clauses.c newexpr->args = args; and they get found just fine in init_sql_fcache. regression=# CREATE OR REPLACE FUNCTION ffp(anyarray) returns anyarray as 'select $1' language 'sql' strict immutable; CREATE FUNCTION regression=# select ffp(array[1]); NOTICE: init_sql_fcache: arg 0, oid 1007 NOTICE: simplify_function: !newexpr = 0, allow_inline = 1 ffp ----- {1} (1 row) When the function is defined as above, getting it to try to inline: regression=# select ffp(array[f]) from (select 1 as f) as ss; NOTICE: simplify_function: !newexpr = 1, allow_inline = 1 NOTICE: inline_function: I'm here NOTICE: init_sql_fcache: arg 0, oid 1007 ffp ----- {1} (1 row) It doesn't get inlined (as defined above) because it fails this check in inline_function(): /* Forget it if declared return type is tuple or void */ result_typtype = get_typtype(funcform->prorettype); if (result_typtype != 'b' && result_typtype != 'd') return NULL; So the only way a problem can arise given the patch I sent, is when the function accepts polymorphic arguments, but does not return polymorphic: regression=# drop FUNCTION ffp(anyarray); DROP FUNCTION regression=# CREATE OR REPLACE FUNCTION ffp(anyarray) returns int[] as 'select array[1]' language 'sql'; CREATE FUNCTION regression=# select ffp(array[f]) from (select 1 as f) as ss; NOTICE: simplify_function: !newexpr = 1, allow_inline = 1 NOTICE: inline_function: I'm here NOTICE: inline_function: simplified ffp ----- {1} (1 row) So I'd propose that we put another check in inline_function(), and reject attempts to inline functions with polymorphic arguments. The other bases are already covered and we already have the proc tuple available in inline_function(). Sound OK? Thanks, Joe
Joe Conway wrote: > Tom Lane wrote: >> Joe Conway <mail@joeconway.com> writes: >>> Included in the patch, I changed SQL language functions so that they >>> could be declared with and use polymorphic types. >> >> I'm not convinced that will work ... in particular, does the parsetree >> get fixed correctly when a SQL function is inlined? > > So I'd propose that we put another check in inline_function(), and > reject attempts to inline functions with polymorphic arguments. The > other bases are already covered and we already have the proc tuple > available in inline_function(). Sound OK? > Here's another copy of the polymorphic (aggregates + SQL functions) patch. This one includes the proposed chage above to ensure polymorphic SQL functions do not get inlined. They can be successfully simplified by evaluate_function() when appropriate, as I showed in the last post. Otherwise, it should be the same. Still compiles clean and passes all regression tests. Please apply. Thanks, Joe Index: src/backend/catalog/pg_aggregate.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/catalog/pg_aggregate.c,v retrieving revision 1.58 diff -c -r1.58 pg_aggregate.c *** src/backend/catalog/pg_aggregate.c 25 Jun 2003 21:30:25 -0000 1.58 --- src/backend/catalog/pg_aggregate.c 29 Jun 2003 19:17:47 -0000 *************** *** 50,59 **** Oid finalfn = InvalidOid; /* can be omitted */ Oid finaltype; Oid fnArgs[FUNC_MAX_ARGS]; ! int nargs; Oid procOid; TupleDesc tupDesc; int i; ObjectAddress myself, referenced; --- 50,65 ---- Oid finalfn = InvalidOid; /* can be omitted */ Oid finaltype; Oid fnArgs[FUNC_MAX_ARGS]; ! int nargs_transfn; ! int nargs_finalfn; Oid procOid; TupleDesc tupDesc; int i; + Oid rettype; + Oid *true_oid_array_transfn; + Oid *true_oid_array_finalfn; + bool retset; + FuncDetailCode fdresult; ObjectAddress myself, referenced; *************** *** 68,91 **** MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid)); fnArgs[0] = aggTransType; if (aggBaseType == ANYOID) ! nargs = 1; else { fnArgs[1] = aggBaseType; ! nargs = 2; } ! transfn = LookupFuncName(aggtransfnName, nargs, fnArgs); if (!OidIsValid(transfn)) ! func_error("AggregateCreate", aggtransfnName, nargs, fnArgs, NULL); tup = SearchSysCache(PROCOID, ObjectIdGetDatum(transfn), 0, 0, 0); if (!HeapTupleIsValid(tup)) ! func_error("AggregateCreate", aggtransfnName, nargs, 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 --- 74,137 ---- MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid)); fnArgs[0] = aggTransType; if (aggBaseType == ANYOID) ! nargs_transfn = 1; else { fnArgs[1] = aggBaseType; ! nargs_transfn = 2; } ! ! /* ! * 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(aggtransfnName, NIL, nargs_transfn, fnArgs, ! &transfn, &rettype, &retset, ! &true_oid_array_transfn); ! ! /* only valid case is a normal function */ ! if (fdresult != FUNCDETAIL_NORMAL) ! func_error("AggregateCreate", aggtransfnName, nargs_transfn, fnArgs, NULL); ! if (!OidIsValid(transfn)) ! func_error("AggregateCreate", aggtransfnName, nargs_transfn, fnArgs, NULL); ! ! /* ! * enforce consistency with ANYARRAY and ANYELEMENT argument ! * and return types, possibly modifying return type along the way ! */ ! rettype = enforce_generic_type_consistency(fnArgs, true_oid_array_transfn, ! nargs_transfn, rettype); ! ! /* ! * func_get_detail will find functions requiring argument type coercion, ! * but we aren't prepared to deal with that ! */ ! if (true_oid_array_transfn[0] != ANYARRAYOID && ! true_oid_array_transfn[0] != ANYELEMENTOID && ! !IsBinaryCoercible(fnArgs[0], true_oid_array_transfn[0])) ! func_error("AggregateCreate", aggtransfnName, nargs_transfn, fnArgs, NULL); ! ! if (nargs_transfn == 2 && ! true_oid_array_transfn[1] != ANYARRAYOID && ! true_oid_array_transfn[1] != ANYELEMENTOID && ! !IsBinaryCoercible(fnArgs[1], true_oid_array_transfn[1])) ! func_error("AggregateCreate", aggtransfnName, nargs_transfn, fnArgs, NULL); ! ! 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_transfn, fnArgs, NULL); proc = (Form_pg_proc) GETSTRUCT(tup); /* * If the transfn is strict and the initval is NULL, make sure input *************** *** 105,121 **** { 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); } else { --- 151,185 ---- { MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid)); fnArgs[0] = aggTransType; ! nargs_finalfn = 1; ! ! fdresult = func_get_detail(aggfinalfnName, NIL, 1, fnArgs, ! &finalfn, &rettype, &retset, ! &true_oid_array_finalfn); ! ! /* only valid case is a normal function */ ! if (fdresult != FUNCDETAIL_NORMAL) ! func_error("AggregateCreate", aggfinalfnName, 1, fnArgs, NULL); ! if (!OidIsValid(finalfn)) func_error("AggregateCreate", aggfinalfnName, 1, fnArgs, NULL); ! ! /* ! * enforce consistency with ANYARRAY and ANYELEMENT argument ! * and return types, possibly modifying return type along the way ! */ ! finaltype = enforce_generic_type_consistency(fnArgs, ! true_oid_array_finalfn, ! nargs_finalfn, rettype); ! ! /* ! * func_get_detail will find functions requiring argument type coercion, ! * but we aren't prepared to deal with that ! */ ! if (true_oid_array_finalfn[0] != ANYARRAYOID && ! true_oid_array_finalfn[0] != ANYELEMENTOID && ! !IsBinaryCoercible(fnArgs[0], true_oid_array_finalfn[0])) func_error("AggregateCreate", aggfinalfnName, 1, fnArgs, NULL); } else { *************** *** 125,130 **** --- 189,222 ---- finaltype = aggTransType; } Assert(OidIsValid(finaltype)); + + /* + * special disallowed cases: + * 1) if finaltype (i.e. aggregate return type) is polymorphic, + * basetype must be polymorphic also + * 2) if finaltype (i.e. aggregate return type) is non-polymorphic, + * and transition function's second argument is non-polymorphic, then + * the transition function's first argument may not be polymorphic + * unless the state type is non-polymorphic + */ + if ((finaltype == ANYARRAYOID || + finaltype == ANYELEMENTOID) && + (aggBaseType != ANYARRAYOID && + aggBaseType != ANYELEMENTOID)) + elog(ERROR, "an aggregate returning ANYARRAY or ANYELEMENT " \ + "must also have either of the them as its base type"); + + + if ((finaltype != ANYARRAYOID && + finaltype != ANYELEMENTOID) && /* rt non-poly */ + (true_oid_array_transfn[0] == ANYARRAYOID || + true_oid_array_transfn[0] == ANYELEMENTOID) && /* tf arg1 poly */ + (true_oid_array_transfn[1] != ANYARRAYOID && + true_oid_array_transfn[1] != ANYELEMENTOID) && /* tf arg2 non-poly */ + (aggTransType == ANYARRAYOID || + aggTransType == ANYELEMENTOID)) /* st arg1 poly */ + elog(ERROR, "the state function's first argument is ambiguous in a " \ + "context that cannot support it"); /* * Everything looks okay. Try to create the pg_proc entry for the Index: src/backend/catalog/pg_proc.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/catalog/pg_proc.c,v retrieving revision 1.97 diff -c -r1.97 pg_proc.c *** src/backend/catalog/pg_proc.c 15 Jun 2003 17:59:10 -0000 1.97 --- src/backend/catalog/pg_proc.c 29 Jun 2003 16:26:36 -0000 *************** *** 377,383 **** typerelid = typeidTypeRelid(rettype); ! if (fn_typtype == 'b' || fn_typtype == 'd') { /* Shouldn't have a typerelid */ Assert(typerelid == InvalidOid); --- 377,386 ---- typerelid = typeidTypeRelid(rettype); ! if (fn_typtype == 'b' || ! fn_typtype == 'd' || ! (fn_typtype == 'p' && rettype == ANYARRAYOID) || ! (fn_typtype == 'p' && rettype == ANYELEMENTOID)) { /* Shouldn't have a typerelid */ Assert(typerelid == InvalidOid); *************** *** 595,610 **** functyptype = get_typtype(proc->prorettype); /* Disallow pseudotypes in arguments and result */ ! /* except that return type can be RECORD or VOID */ if (functyptype == 'p' && proc->prorettype != RECORDOID && ! proc->prorettype != VOIDOID) elog(ERROR, "SQL functions cannot return type %s", format_type_be(proc->prorettype)); for (i = 0; i < proc->pronargs; i++) { ! if (get_typtype(proc->proargtypes[i]) == 'p') elog(ERROR, "SQL functions cannot have arguments of type %s", format_type_be(proc->proargtypes[i])); } --- 598,617 ---- functyptype = get_typtype(proc->prorettype); /* Disallow pseudotypes in arguments and result */ ! /* except that return type can be RECORD, VOID, ANYARRAY, or ANYELEMENT */ if (functyptype == 'p' && proc->prorettype != RECORDOID && ! proc->prorettype != VOIDOID && ! proc->prorettype != ANYARRAYOID && ! proc->prorettype != ANYELEMENTOID) elog(ERROR, "SQL functions cannot return type %s", format_type_be(proc->prorettype)); for (i = 0; i < proc->pronargs; i++) { ! if (get_typtype(proc->proargtypes[i]) == 'p' && ! proc->proargtypes[i] != ANYARRAYOID && ! proc->proargtypes[i] != ANYELEMENTOID) elog(ERROR, "SQL functions cannot have arguments of type %s", format_type_be(proc->proargtypes[i])); } Index: src/backend/commands/aggregatecmds.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/commands/aggregatecmds.c,v retrieving revision 1.8 diff -c -r1.8 aggregatecmds.c *** src/backend/commands/aggregatecmds.c 27 Jun 2003 14:45:27 -0000 1.8 --- src/backend/commands/aggregatecmds.c 29 Jun 2003 16:26:36 -0000 *************** *** 120,126 **** baseTypeId = typenameTypeId(baseType); transTypeId = typenameTypeId(transType); ! if (get_typtype(transTypeId) == 'p') elog(ERROR, "Aggregate transition datatype cannot be %s", format_type_be(transTypeId)); --- 120,128 ---- baseTypeId = typenameTypeId(baseType); transTypeId = typenameTypeId(transType); ! if (get_typtype(transTypeId) == 'p' && ! transTypeId != ANYARRAYOID && ! transTypeId != ANYELEMENTOID) elog(ERROR, "Aggregate transition datatype cannot be %s", format_type_be(transTypeId)); Index: src/backend/executor/functions.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/executor/functions.c,v retrieving revision 1.66 diff -c -r1.66 functions.c *** src/backend/executor/functions.c 12 Jun 2003 17:29:26 -0000 1.66 --- src/backend/executor/functions.c 30 Jun 2003 01:19:42 -0000 *************** *** 20,25 **** --- 20,26 ---- #include "executor/execdefs.h" #include "executor/executor.h" #include "executor/functions.h" + #include "parser/parse_expr.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "tcop/utility.h" *************** *** 212,221 **** if (nargs > 0) { argOidVect = (Oid *) palloc(nargs * sizeof(Oid)); ! memcpy(argOidVect, ! procedureStruct->proargtypes, ! nargs * sizeof(Oid)); } else argOidVect = (Oid *) NULL; --- 213,235 ---- if (nargs > 0) { + List *p; + int argnum = 0; + argOidVect = (Oid *) palloc(nargs * sizeof(Oid)); ! if (finfo->fn_expr) ! { ! /* ! * If we have a function expression node available to us ! * use it, as any polymorphic types should have been ! * disambiguated for us already ! */ ! foreach(p, ((FuncExpr *) finfo->fn_expr)->args) ! argOidVect[argnum++] = exprType((Node *) lfirst(p)); ! } ! else ! memcpy(argOidVect, procedureStruct->proargtypes, ! nargs * sizeof(Oid)); } else argOidVect = (Oid *) NULL; Index: src/backend/executor/nodeAgg.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/executor/nodeAgg.c,v retrieving revision 1.109 diff -c -r1.109 nodeAgg.c *** src/backend/executor/nodeAgg.c 25 Jun 2003 21:30:28 -0000 1.109 --- src/backend/executor/nodeAgg.c 29 Jun 2003 16:26:36 -0000 *************** *** 59,64 **** --- 59,65 ---- #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" *************** *** 1187,1193 **** --- 1188,1199 ---- AclResult aclresult; Oid transfn_oid, finalfn_oid; + FuncExpr *transfnexpr, + *finalfnexpr; Datum textInitVal; + List *fargs; + Oid agg_rt_basetype; + Oid transfn_arg1_type; int i; /* Planner should have assigned aggregate to correct level */ *************** *** 1238,1243 **** --- 1244,1274 ---- &peraggstate->transtypeLen, &peraggstate->transtypeByVal); + peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn; + peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn; + + /* get the runtime aggregate argument type */ + fargs = aggref->args; + agg_rt_basetype = exprType((Node *) nth(0, fargs)); + + expand_aggregate(agg_rt_basetype, + aggform->aggtranstype, + aggref->aggfnoid, + transfn_oid, + finalfn_oid, + &transfnexpr, + &finalfnexpr, + &transfn_arg1_type); + + 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; + } + /* * initval is potentially null, so don't try to access it as a * struct field. Must do it the hard way with SysCacheGetAttr. *************** *** 1250,1263 **** 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); /* * If the transfn is strict and the initval is NULL, make sure --- 1281,1287 ---- peraggstate->initValue = (Datum) 0; else peraggstate->initValue = GetAggInitVal(textInitVal, ! transfn_arg1_type); /* * If the transfn is strict and the initval is NULL, make sure Index: src/backend/nodes/copyfuncs.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/nodes/copyfuncs.c,v retrieving revision 1.258 diff -c -r1.258 copyfuncs.c *** src/backend/nodes/copyfuncs.c 29 Jun 2003 00:33:43 -0000 1.258 --- src/backend/nodes/copyfuncs.c 29 Jun 2003 16:26:36 -0000 *************** *** 728,733 **** --- 728,734 ---- COPY_SCALAR_FIELD(agglevelsup); COPY_SCALAR_FIELD(aggstar); COPY_SCALAR_FIELD(aggdistinct); + COPY_NODE_FIELD(args); return newnode; } Index: src/backend/nodes/equalfuncs.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/nodes/equalfuncs.c,v retrieving revision 1.201 diff -c -r1.201 equalfuncs.c *** src/backend/nodes/equalfuncs.c 29 Jun 2003 00:33:43 -0000 1.201 --- src/backend/nodes/equalfuncs.c 29 Jun 2003 16:26:36 -0000 *************** *** 205,210 **** --- 205,211 ---- COMPARE_SCALAR_FIELD(agglevelsup); COMPARE_SCALAR_FIELD(aggstar); COMPARE_SCALAR_FIELD(aggdistinct); + COMPARE_NODE_FIELD(args); return true; } Index: src/backend/nodes/outfuncs.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/nodes/outfuncs.c,v retrieving revision 1.211 diff -c -r1.211 outfuncs.c *** src/backend/nodes/outfuncs.c 29 Jun 2003 00:33:43 -0000 1.211 --- src/backend/nodes/outfuncs.c 29 Jun 2003 16:26:36 -0000 *************** *** 616,621 **** --- 616,622 ---- WRITE_UINT_FIELD(agglevelsup); WRITE_BOOL_FIELD(aggstar); WRITE_BOOL_FIELD(aggdistinct); + WRITE_NODE_FIELD(args); } static void Index: src/backend/nodes/readfuncs.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/nodes/readfuncs.c,v retrieving revision 1.157 diff -c -r1.157 readfuncs.c *** src/backend/nodes/readfuncs.c 29 Jun 2003 00:33:43 -0000 1.157 --- src/backend/nodes/readfuncs.c 29 Jun 2003 16:26:36 -0000 *************** *** 416,421 **** --- 416,422 ---- READ_UINT_FIELD(agglevelsup); READ_BOOL_FIELD(aggstar); READ_BOOL_FIELD(aggdistinct); + READ_NODE_FIELD(args); READ_DONE(); } Index: src/backend/optimizer/util/clauses.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/optimizer/util/clauses.c,v retrieving revision 1.142 diff -c -r1.142 clauses.c *** src/backend/optimizer/util/clauses.c 29 Jun 2003 00:33:43 -0000 1.142 --- src/backend/optimizer/util/clauses.c 30 Jun 2003 01:26:30 -0000 *************** *** 133,138 **** --- 133,160 ---- } /***************************************************************************** + * FUNCTION clause functions + *****************************************************************************/ + + /* + * make_funcclause + * Creates a function clause given its function info and argument list. + */ + Expr * + make_funcclause(Oid funcid, Oid funcresulttype, bool funcretset, + CoercionForm funcformat, List *funcargs) + { + FuncExpr *expr = makeNode(FuncExpr); + + expr->funcid = funcid; + expr->funcresulttype = funcresulttype; + expr->funcretset = funcretset; + expr->funcformat = funcformat; + expr->args = funcargs; + return (Expr *) expr; + } + + /***************************************************************************** * NOT clause functions *****************************************************************************/ *************** *** 1731,1736 **** --- 1753,1759 ---- int *usecounts; List *arg; int i; + int j; /* * Forget it if the function is not SQL-language or has other *************** *** 1742,1752 **** funcform->pronargs != length(args)) return NULL; ! /* Forget it if declared return type is tuple or void */ result_typtype = get_typtype(funcform->prorettype); if (result_typtype != 'b' && result_typtype != 'd') return NULL; /* Check for recursive function, and give up trying to expand if so */ if (oidMember(funcid, active_fns)) --- 1765,1783 ---- funcform->pronargs != length(args)) return NULL; ! /* Forget it if declared return type is not base or domain */ result_typtype = get_typtype(funcform->prorettype); if (result_typtype != 'b' && result_typtype != 'd') return NULL; + + /* Forget it if any declared argument type is polymorphic */ + for (j = 0; j < funcform->pronargs; j++) + { + if (funcform->proargtypes[j] == ANYARRAYOID || + funcform->proargtypes[j] == ANYELEMENTOID) + return NULL; + } /* Check for recursive function, and give up trying to expand if so */ if (oidMember(funcid, active_fns)) Index: src/backend/parser/parse_agg.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/parser/parse_agg.c,v retrieving revision 1.53 diff -c -r1.53 parse_agg.c *** src/backend/parser/parse_agg.c 6 Jun 2003 15:04:02 -0000 1.53 --- src/backend/parser/parse_agg.c 29 Jun 2003 16:26:36 -0000 *************** *** 14,25 **** --- 14,29 ---- */ #include "postgres.h" + #include "catalog/pg_type.h" + #include "nodes/params.h" #include "optimizer/clauses.h" #include "optimizer/tlist.h" #include "optimizer/var.h" #include "parser/parse_agg.h" + #include "parser/parse_type.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" + #include "utils/lsyscache.h" typedef struct *************** *** 312,314 **** --- 316,539 ---- return expression_tree_walker(node, check_ungrouped_columns_walker, (void *) context); } + + /* + * Create function expressions for the transition and final functions + * of an aggregate so that they can be attached to the FmgrInfo nodes + * of AggStatePerAgg. If we didn't, the functions would not be able to + * know what argument and return data types to use for any that are + * polymorphic in its definition. + */ + void + expand_aggregate(Oid agg_rt_basetype, + Oid agg_statetype, + Oid agg_fnoid, + Oid transfn_oid, + Oid finalfn_oid, + FuncExpr **transfnexpr, + FuncExpr **finalfnexpr, + Oid *transfn_arg1_type) + { + Oid *transfn_arg_types; + List *transfn_args = NIL; + int transfn_nargs; + Oid transfn_ret_type; + Oid *finalfn_arg_types = NULL; + List *finalfn_args = NIL; + Oid finalfn_ret_type = InvalidOid; + int finalfn_nargs = 0; + Param *arg0; + Param *arg1; + + /* get the transition function argument and return types */ + transfn_ret_type = get_func_rettype(transfn_oid); + transfn_arg_types = get_func_argtypes(transfn_oid, &transfn_nargs); + + /* resolve any polymorphic types */ + if (transfn_nargs == 2) + { + /* base type was not ANY */ + if ((transfn_arg_types[0] == ANYARRAYOID || + transfn_arg_types[0] == ANYELEMENTOID) && + (transfn_arg_types[1] == ANYARRAYOID || + transfn_arg_types[1] == ANYELEMENTOID)) + { + /* + * If both transfn args are polymorphic, we can + * resolve transfn arg 1 using base type as context + */ + transfn_arg_types[0] = resolve_type(transfn_arg_types[0], + agg_rt_basetype); + } + else if ((transfn_arg_types[0] == ANYARRAYOID || + transfn_arg_types[0] == ANYELEMENTOID)) + { + /* + * Otherwise, if transfn arg 1 is polymorphic, we can + * resolve it using state type as context. This is only + * safe because we prevented the situation where both + * state type and transfn arg 1 are polymorphic with a + * non-polymorphic transfn arg 2, during aggregate creation. + */ + transfn_arg_types[0] = resolve_type(transfn_arg_types[0], + agg_statetype); + } + + /* + * Now, if transfn arg 2 is polymorphic, we can set it to the runtime + * base type without further adieu + */ + if (transfn_arg_types[1] == ANYARRAYOID || + transfn_arg_types[1] == ANYELEMENTOID) + transfn_arg_types[1] = agg_rt_basetype; + + /* + * Build arg list to use on the transfn FuncExpr node. We really + * only care that transfn can discover the actual argument types + * at runtime using get_fn_expr_argtype() + */ + arg0 = makeNode(Param); + arg0->paramkind = PARAM_EXEC; + arg0->paramid = -1; + arg0->paramtype = transfn_arg_types[0]; + + arg1 = makeNode(Param); + arg1->paramkind = PARAM_EXEC; + arg1->paramid = -1; + arg1->paramtype = transfn_arg_types[1]; + + transfn_args = makeList2(arg0, arg1); + + /* + * the state transition function always returns the same type + * as its first argument + */ + if (transfn_ret_type == ANYARRAYOID || + transfn_ret_type == ANYELEMENTOID) + transfn_ret_type = transfn_arg_types[0]; + } + else if (transfn_nargs == 1) + /* + * base type was ANY, therefore the aggregate return type should + * be non-polymorphic + */ + { + Oid finaltype = get_func_rettype(agg_fnoid); + + /* + * this should have been prevented in AggregateCreate, + * but check anyway + */ + if (finaltype == ANYARRAYOID || finaltype == ANYELEMENTOID) + elog(ERROR, "an aggregate returning ANYARRAY or ANYELEMENT " \ + "must also have either of the them as its base type"); + + /* see if we have a final function */ + if (OidIsValid(finalfn_oid)) + { + finalfn_arg_types = get_func_argtypes(finalfn_oid, &finalfn_nargs); + if (finalfn_nargs != 1) + elog(ERROR, "final function takes unexpected number " \ + "of arguments: %d", finalfn_nargs); + + /* + * final function argument is always the same as the state + * function return type + */ + if (finalfn_arg_types[0] != ANYARRAYOID && + finalfn_arg_types[0] != ANYELEMENTOID) + { + /* if it is not ambiguous, use it */ + transfn_ret_type = finalfn_arg_types[0]; + } + else + { + /* if it is ambiguous, try to derive it */ + finalfn_ret_type = finaltype; + finalfn_arg_types[0] = resolve_type(finalfn_arg_types[0], + finalfn_ret_type); + transfn_ret_type = finalfn_arg_types[0]; + } + } + else + transfn_ret_type = finaltype; + + transfn_arg_types[0] = resolve_type(transfn_arg_types[0], + transfn_ret_type); + + /* + * Build arg list to use on the transfn FuncExpr node. We really + * only care that transfn can discover the actual argument types + * at runtime using get_fn_expr_argtype() + */ + arg0 = makeNode(Param); + arg0->paramkind = PARAM_EXEC; + arg0->paramid = -1; + arg0->paramtype = transfn_arg_types[0]; + + transfn_args = makeList1(arg0); + } + else + elog(ERROR, "state transition function takes unexpected number " \ + "of arguments: %d", transfn_nargs); + + if (OidIsValid(finalfn_oid)) + { + /* get the final function argument and return types */ + if (finalfn_ret_type == InvalidOid) + finalfn_ret_type = get_func_rettype(finalfn_oid); + + if (!finalfn_arg_types) + { + finalfn_arg_types = get_func_argtypes(finalfn_oid, &finalfn_nargs); + if (finalfn_nargs != 1) + elog(ERROR, "final function takes unexpected number " \ + "of arguments: %d", finalfn_nargs); + } + + /* + * final function argument is always the same as the state + * function return type, which by now should have been resolved + */ + if (finalfn_arg_types[0] == ANYARRAYOID || + finalfn_arg_types[0] == ANYELEMENTOID) + finalfn_arg_types[0] = transfn_ret_type; + + /* + * Build arg list to use on the finalfn FuncExpr node. We really + * only care that finalfn can discover the actual argument types + * at runtime using get_fn_expr_argtype() + */ + arg0 = makeNode(Param); + arg0->paramkind = PARAM_EXEC; + arg0->paramid = -1; + arg0->paramtype = finalfn_arg_types[0]; + + finalfn_args = makeList1(arg0); + + finalfn_ret_type = resolve_type(finalfn_ret_type, + finalfn_arg_types[0]); + } + + *transfnexpr = (FuncExpr *) make_funcclause(transfn_oid, + transfn_ret_type, + false, + COERCE_DONTCARE, + transfn_args); + + if (OidIsValid(finalfn_oid)) + { + *finalfnexpr = (FuncExpr *) make_funcclause(finalfn_oid, + finalfn_ret_type, + false, + COERCE_DONTCARE, + finalfn_args); + } + + /* + * we need to return the resolved transfn arg1 type to be used + * by GetAggInitVal + */ + *transfn_arg1_type = transfn_arg_types[0]; + } + Index: src/backend/parser/parse_coerce.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/parser/parse_coerce.c,v retrieving revision 2.101 diff -c -r2.101 parse_coerce.c *** src/backend/parser/parse_coerce.c 27 Jun 2003 00:33:25 -0000 2.101 --- src/backend/parser/parse_coerce.c 29 Jun 2003 16:26:36 -0000 *************** *** 859,865 **** /* Get the element type based on the array type, if we have one */ if (OidIsValid(array_typeid)) { ! array_typelem = get_element_type(array_typeid); if (!OidIsValid(array_typelem)) elog(ERROR, "Argument declared ANYARRAY is not an array: %s", format_type_be(array_typeid)); --- 859,869 ---- /* Get the element type based on the array type, if we have one */ if (OidIsValid(array_typeid)) { ! if (array_typeid != ANYARRAYOID) ! array_typelem = get_element_type(array_typeid); ! else ! array_typelem = ANYELEMENTOID; ! if (!OidIsValid(array_typelem)) elog(ERROR, "Argument declared ANYARRAY is not an array: %s", format_type_be(array_typeid)); *************** *** 919,925 **** { if (!OidIsValid(array_typeid)) { ! array_typeid = get_array_type(elem_typeid); if (!OidIsValid(array_typeid)) elog(ERROR, "Cannot find array type for datatype %s", format_type_be(elem_typeid)); --- 923,933 ---- { if (!OidIsValid(array_typeid)) { ! if (elem_typeid != ANYELEMENTOID) ! array_typeid = get_array_type(elem_typeid); ! else ! array_typeid = ANYARRAYOID; ! if (!OidIsValid(array_typeid)) elog(ERROR, "Cannot find array type for datatype %s", format_type_be(elem_typeid)); Index: src/backend/parser/parse_func.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/parser/parse_func.c,v retrieving revision 1.152 diff -c -r1.152 parse_func.c *** src/backend/parser/parse_func.c 25 Jun 2003 21:30:31 -0000 1.152 --- src/backend/parser/parse_func.c 29 Jun 2003 16:26:36 -0000 *************** *** 336,341 **** --- 336,342 ---- aggref->target = lfirst(fargs); aggref->aggstar = agg_star; aggref->aggdistinct = agg_distinct; + aggref->args = fargs; /* parse_agg.c does additional aggregate-specific processing */ transformAggregateCall(pstate, aggref); Index: src/backend/parser/parse_type.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/parser/parse_type.c,v retrieving revision 1.57 diff -c -r1.57 parse_type.c *** src/backend/parser/parse_type.c 29 Apr 2003 22:13:10 -0000 1.57 --- src/backend/parser/parse_type.c 29 Jun 2003 16:26:36 -0000 *************** *** 484,486 **** --- 484,536 ---- pfree(buf.data); } + + /* + * Given a type_to_resolve oid, typically defined at function creation + * (e.g. a function argument or return type), and context_type oid, + * typically gleaned by the parser as one of the actual arguments + * at function call time, derive the runtime type of type_to_resolve. + * The intent is to use runtime context to determine what type we should + * assign to a polymorphic argument or return type. + * + * The rules for this resolution are as follows: + * 1) if the context type is polymorphic, punt and return type_to_resolve + * unchanged + * 2) if type_to_resolve is ANYARRAY (polymorphic), then return context_type + * if it is already an array type, or get its array type if not + * 3) if type_to_resolve is ANYELEMENT (polymorphic), then return context_type + * if it is already an elemental type, or get its element type if not + * 4) if type_to_resolve is non-polymorphic, return it unchanged + */ + Oid + resolve_type(Oid type_to_resolve, Oid context_type) + { + Oid resolved_type; + + if (context_type == ANYARRAYOID || context_type == ANYELEMENTOID) + resolved_type = type_to_resolve; + else if (type_to_resolve == ANYARRAYOID) + /* any array */ + { + Oid context_type_arraytype = get_array_type(context_type); + + if (context_type_arraytype != InvalidOid) + resolved_type = context_type_arraytype; + else + resolved_type = context_type; + } + else if (type_to_resolve == ANYELEMENTOID) + /* any element */ + { + Oid context_type_elemtype = get_element_type(context_type); + + if (context_type_elemtype != InvalidOid) + resolved_type = context_type_elemtype; + else + resolved_type = context_type; + } + else + resolved_type = type_to_resolve; + + return resolved_type; + } Index: src/backend/utils/cache/lsyscache.c =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/backend/utils/cache/lsyscache.c,v retrieving revision 1.100 diff -c -r1.100 lsyscache.c *** src/backend/utils/cache/lsyscache.c 27 Jun 2003 00:33:25 -0000 1.100 --- src/backend/utils/cache/lsyscache.c 29 Jun 2003 16:26:36 -0000 *************** *** 719,724 **** --- 719,758 ---- } /* + * get_func_argtypes + * Given procedure id, return the function's argument types. + * Also pass back the number of arguments. + */ + Oid * + get_func_argtypes(Oid funcid, int *nargs) + { + HeapTuple tp; + Form_pg_proc procstruct; + Oid *result = NULL; + int i; + + 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); + *nargs = (int) procstruct->pronargs; + + if (*nargs > 0) + { + result = (Oid *) palloc(*nargs * sizeof(Oid)); + + for (i = 0; i < *nargs; i++) + result[i] = procstruct->proargtypes[i]; + } + + ReleaseSysCache(tp); + return result; + } + + /* * get_func_retset * Given procedure id, return the function's proretset flag. */ Index: src/include/nodes/primnodes.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/nodes/primnodes.h,v retrieving revision 1.86 diff -c -r1.86 primnodes.h *** src/include/nodes/primnodes.h 29 Jun 2003 00:33:44 -0000 1.86 --- src/include/nodes/primnodes.h 29 Jun 2003 16:26:36 -0000 *************** *** 226,231 **** --- 226,232 ---- Index agglevelsup; /* > 0 if agg belongs to outer query */ bool aggstar; /* TRUE if argument was really '*' */ bool aggdistinct; /* TRUE if it's agg(DISTINCT ...) */ + List *args; /* arguments to the aggregate */ } Aggref; /* ---------------- Index: src/include/optimizer/clauses.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/optimizer/clauses.h,v retrieving revision 1.65 diff -c -r1.65 clauses.h *** src/include/optimizer/clauses.h 25 Jun 2003 21:30:33 -0000 1.65 --- src/include/optimizer/clauses.h 29 Jun 2003 16:26:36 -0000 *************** *** 28,33 **** --- 28,36 ---- extern Node *get_leftop(Expr *clause); extern Node *get_rightop(Expr *clause); + extern Expr *make_funcclause(Oid funcid, Oid funcresulttype, bool funcretset, + CoercionForm funcformat, List *funcargs); + extern bool not_clause(Node *clause); extern Expr *make_notclause(Expr *notclause); extern Expr *get_notclausearg(Expr *notclause); Index: src/include/parser/parse_agg.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/parser/parse_agg.h,v retrieving revision 1.26 diff -c -r1.26 parse_agg.h *** src/include/parser/parse_agg.h 6 Jun 2003 15:04:03 -0000 1.26 --- src/include/parser/parse_agg.h 29 Jun 2003 16:26:36 -0000 *************** *** 18,22 **** --- 18,30 ---- extern void transformAggregateCall(ParseState *pstate, Aggref *agg); extern void parseCheckAggregates(ParseState *pstate, Query *qry); + extern void expand_aggregate(Oid agg_rt_basetype, + Oid agg_statetype, + Oid agg_fnoid, + Oid transfn_oid, + Oid finalfn_oid, + FuncExpr **transfnexpr, + FuncExpr **finalfnexpr, + Oid *transfn_arg1_type); #endif /* PARSE_AGG_H */ Index: src/include/parser/parse_type.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/parser/parse_type.h,v retrieving revision 1.24 diff -c -r1.24 parse_type.h *** src/include/parser/parse_type.h 31 Aug 2002 22:10:47 -0000 1.24 --- src/include/parser/parse_type.h 29 Jun 2003 16:26:36 -0000 *************** *** 40,45 **** --- 40,46 ---- extern Oid typeidTypeRelid(Oid type_id); extern void parseTypeString(const char *str, Oid *type_id, int32 *typmod); + extern Oid resolve_type(Oid type_to_resolve, Oid context_type); #define ISCOMPLEX(typeid) (typeidTypeRelid(typeid) != InvalidOid) Index: src/include/utils/lsyscache.h =================================================================== RCS file: /opt/src/cvs/pgsql-server/src/include/utils/lsyscache.h,v retrieving revision 1.75 diff -c -r1.75 lsyscache.h *** src/include/utils/lsyscache.h 27 Jun 2003 00:33:26 -0000 1.75 --- src/include/utils/lsyscache.h 29 Jun 2003 16:26:36 -0000 *************** *** 50,55 **** --- 50,56 ---- extern RegProcedure get_oprjoin(Oid opno); extern char *get_func_name(Oid funcid); extern Oid get_func_rettype(Oid funcid); + extern Oid *get_func_argtypes(Oid funcid, int *nargs); extern bool get_func_retset(Oid funcid); extern bool func_strict(Oid funcid); extern char func_volatile(Oid funcid);
Joe Conway <mail@joeconway.com> writes: > So I'd propose that we put another check in inline_function(), and > reject attempts to inline functions with polymorphic arguments. Seems reasonable. Someday we might want to try to make that work, but not the day before feature freeze... regards, tom lane
Joe Conway <mail@joeconway.com> writes: + * The rules for this resolution are as follows: + * 1) if the context type is polymorphic, punt and return type_to_resolve + * unchanged + * 2) if type_to_resolve is ANYARRAY (polymorphic), then return context_type + * if it is already an array type, or get its array type if not + * 3) if type_to_resolve is ANYELEMENT (polymorphic), then return context_type + * if it is already an elemental type, or get its element type if not + * 4) if type_to_resolve is non-polymorphic, return it unchanged + */ + Oid + resolve_type(Oid type_to_resolve, Oid context_type) This seems wrong. ANYELEMENT doesn't imply conversion from array to element type. I don't think you're giving resolve_type nearly enough context to produce a correct answer. [ thinks for a bit ] Bearing in mind that the result type of the transfn must equal its first input type, ISTM there are only four interesting cases for polymorphic transfer functions: transfn(anyelement, anyelement) returns anyelement transfn(anyelement, anyarray) returns anyelement transfn(anyarray, anyelement) returns anyarray transfn(anyarray, anyarray) returns anyarray Per our previous discussion, other cases (such as single-input transfn or non-polymorphic second input type) can be rejected by CREATE AGGREGATE since there'd be no way to resolve the actual transfer state type. Knowing which of these four cases you have, you can correctly derive the actual state type from the actual aggregate input type, namely 1. same as input type (no restrictions on what it is) 2. element type of input (which must be an array type) 3. array type with input as element (there must be one) 4. input type, but first check it's an array You're not providing enough info to resolve_type to let it handle all four cases correctly. In any case, this procedure seems exceedingly specific to the problem of resolving aggregate internal types. I doubt it should be in parse_type at all, and it certainly shouldn't have a name as generic as resolve_type. regards, tom lane
Tom Lane wrote: > Joe Conway <mail@joeconway.com> writes: > > + * The rules for this resolution are as follows: > + * 1) if the context type is polymorphic, punt and return type_to_resolve > + * unchanged > + * 2) if type_to_resolve is ANYARRAY (polymorphic), then return context_type > + * if it is already an array type, or get its array type if not > + * 3) if type_to_resolve is ANYELEMENT (polymorphic), then return context_type > + * if it is already an elemental type, or get its element type if not > + * 4) if type_to_resolve is non-polymorphic, return it unchanged > + */ > + Oid > + resolve_type(Oid type_to_resolve, Oid context_type) > > This seems wrong. ANYELEMENT doesn't imply conversion from array to > element type. I don't think you're giving resolve_type nearly enough > context to produce a correct answer. And the function isn't trying to do that. If I have an ANYELEMENT I'm trying to resolve, and the context type is a scalar type, it uses that. If the context type is array, then it uses the array's element type. > You're not providing enough info to resolve_type to let it handle all > four cases correctly. In any case, this procedure seems exceedingly > specific to the problem of resolving aggregate internal types. I doubt > it should be in parse_type at all, and it certainly shouldn't have a > name as generic as resolve_type. > No, resolve_type() is not at all specific to polymorphic aggregates. It implements the rules of polymorphism that we previously agreed to, namely that an ANYARRAY can be resolved by knowing any of these data types at the time of function call: 1) the actual call type at the same position (i.e. argument number or return type) as the ANYARRAY you're trying to resolve 2) the actual call type at a different position from the ANYARRAY you're trying to resolve, as long as the declared type at that position is either ANYARRAY or ANYELEMENT. - If type_to_resolve is non-polymorphic, we have nothing in need of resolution. - If context_type is polymorphic, we have no context with which to do resolution. - If type_to_resolve is polymorphic (ANYARRAY or ANYELEMENT), and context_type is not, resolve_type() picks the appropriate type based on whether we need an array or not, and whether we've been given an array or not as context. Did you find a specific case where this falls down? Joe
Joe Conway <mail@joeconway.com> writes: > Tom Lane wrote: >> You're not providing enough info to resolve_type to let it handle all >> four cases correctly. > No, resolve_type() is not at all specific to polymorphic aggregates. It > implements the rules of polymorphism that we previously agreed to, > namely that an ANYARRAY can be resolved by knowing any of these data > types at the time of function call: > 1) the actual call type at the same position (i.e. argument number or > return type) as the ANYARRAY you're trying to resolve > 2) the actual call type at a different position from the ANYARRAY you're > trying to resolve, as long as the declared type at that position is > either ANYARRAY or ANYELEMENT. But you still need three pieces of info, and it's only being given two. In the second case (where you know actual argument type at a different position) you must know whether the other position's declared type is anyarray or anyelement, and you can't assume it's the same as the one at the position you want to resolve. > Did you find a specific case where this falls down? It fails to raise errors in cases where errors should be raised, but instead delivers the wrong datatype as result. It can also incorrectly replace an arraytype by its element type ("ANYELEMENT" doesn't require the actual type to not be an array --- unless "ANYARRAY" is also used in the same declaration, and even then it's only going to fail because we don't support arrays of arrays). regards, tom lane
Tom Lane wrote: > In the second case (where you know actual argument type at a different > position) you must know whether the other position's declared type is > anyarray or anyelement, and you can't assume it's the same as the one at > the position you want to resolve. I still don't understand why that's needed (but perhaps it's related to your comment below). > It can also incorrectly replace an arraytype by its element type > ("ANYELEMENT" doesn't require the actual type to not be an array Are you referring to ANYELEMENT actually being an array at runtime? That's the first time I've heard that concept. 'Til now, I've been working with the assumption that arrays and elements were distinct, and one can imply the other. > --- unless > "ANYARRAY" is also used in the same declaration, and even then > it's only going to fail because we don't support arrays of arrays). But this is the least of our problems when/if we support arrays of arrays. The notion of element types being distinct from array types goes pretty deep currently. In any case, can you suggest concrete changes I can work on between now and tonight? Or can this go in before the freeze as-is and get adjusted afterwards? Joe
Joe Conway <mail@joeconway.com> writes: > Are you referring to ANYELEMENT actually being an array at runtime? > That's the first time I've heard that concept. That was the behavior we agreed to some time ago, to avoid needing to entangle ANY into the polymorphism logic. See the comments for check_generic_type_consistency: * The argument consistency rules are: * * 1) All arguments declared ANYARRAY must have matching datatypes, * and must in fact be varlena arrays. * 2) All arguments declared ANYELEMENT must have matching datatypes. * 3) If there are arguments of both ANYELEMENT and ANYARRAY, make sure * the actual ANYELEMENT datatype is in fact the element type for * the actual ANYARRAY datatype. If only ANYELEMENT and not ANYARRAY appears in a function declaration, then it can stand for any type, because only rule 2 applies. (The difference from ANY is that multiple occurences of ANYELEMENT are all constrained to stand for the same type.) regards, tom lane
Tom Lane wrote: > If only ANYELEMENT and not ANYARRAY appears in a function declaration, > then it can stand for any type, because only rule 2 applies. (The > difference from ANY is that multiple occurences of ANYELEMENT are all > constrained to stand for the same type.) Hmmm, I don't remember that nuance, hence the code deficiency. I'm should be able to finish up the plpgsql hash table stuff in the next couple of hours, then I'll get back to thinking about this one. Do I have until midnite PDT, or EDT? Joe
Joe Conway <mail@joeconway.com> writes: > Do I have until midnite PDT, or EDT? We hadn't actually set a formal deadline time AFAIR. Midnight your local time is fine with me. Thinking about this further, it occurs to me that there's no hard reason plpgsql (and other PLs that adopt the we-can-convert-anything-to-string philosophy, such as pltcl) couldn't allow arguments (though not results) of type ANY. It's not different from accepting ANYELEMENT as far as the runtime mechanisms are concerned. The only difference is in constraining multiple arguments and/or the result to be of the same or related types, which is not really an issue that affects the PL directly. As far as the other point goes, I plan to change resolve_type to be like resolve_polymorphic_type(declared_type, context_actual_type, context_declared_type) where context_actual_type is the actual datatype passed to an argument of declared type context_declared_type, and declared_type is the declared type of some argument or result that you want to resolve (not necessarily the same argument). So the rules are 1. declared_type not polymorphic -> return it as-is. 2. declared_type polymorphic, but context_declared_type not polymorphic -> raise error ("can't resolve"). 3. Otherwise there are four possible combinations: declared_type context_declared_type action ANYELEMENT ANYELEMENT return context_actual_type ANYELEMENT ANYARRAY return get_element_type(context_actual_type) (raise error if it fails) ANYARRAY ANYELEMENT return get_array_type(context_actual_type) (raise error if it fails) ANYARRAY ANYARRAY check context_actual_type is an array, then return it This should work as long as the parser has previously done enforce_generic_type_consistency on the call. I'm still not convinced that there is any application for it outside of deriving a polymorphic aggregate's state type, though. regards, tom lane
Tom Lane wrote: > Thinking about this further, it occurs to me that there's no hard reason > plpgsql (and other PLs that adopt the we-can-convert-anything-to-string > philosophy, such as pltcl) couldn't allow arguments (though not results) > of type ANY. It's not different from accepting ANYELEMENT as far as the > runtime mechanisms are concerned. The only difference is in > constraining multiple arguments and/or the result to be of the same or > related types, which is not really an issue that affects the PL directly. True. As long as the function has access to the runtime data type, it has the ability to deal with anything it's handed. > As far as the other point goes, I plan to change resolve_type to be like OK, that all makes good sense to me now. > I'm still not convinced that there is any application for it outside of > deriving a polymorphic aggregate's state type, though. At least not yet ;-) Between this discussion, and Peter pointing out that the spec allows arrays-of-arrays, it's gotten me thinking about how to implement said arrays-of-arrays. Obviously not gonna happen for 7.4, but I might try to do that, fix the NULL array element issue, and otherwise try to continue the progress on SQL99 array support for 7.5. Joe
Joe Conway <mail@joeconway.com> writes: > Attached is a patch that implements polymorphic aggregates. > Included in the patch, I changed SQL language functions so that they > could be declared with and use polymorphic types. I've committed the polymorphic-SQL-functions part of this separately. I didn't like the way you did it --- in particular, hacking enforce_generic_type_consistency to allow generic types to be returned strikes me as a very dangerous thing; it's supposed to be replacing them all, not passing them back. In any case it didn't get the job done, since any but the most trivial function bodies would fail type resolution at some point. For example, I tried create function array_sub(anyarray, int) returns anyelement as 'select $1[$2]' language sql; and it failed with something along the lines of ERROR: transformArraySubscripts: type anyarray is not an array What I've done instead is not to weaken type checking, but simply to postpone all checking of the body of a SQL function to runtime if it has any polymorphic arguments. At runtime, we know the actual types for the arguments, and we know the actual assigned result type, and then we can run the normal checking operations without any problem. Applied patch attached, just FYI. (It still needs documentation updates, which I trust you will supply later.) Now back to looking at polymorphic aggregates... regards, tom lane *** src/backend/catalog/pg_proc.c.orig Sun Jun 15 13:59:10 2003 --- src/backend/catalog/pg_proc.c Mon Jun 30 19:45:01 2003 *************** *** 33,39 **** #include "utils/syscache.h" - static void checkretval(Oid rettype, char fn_typtype, List *queryTreeList); Datum fmgr_internal_validator(PG_FUNCTION_ARGS); Datum fmgr_c_validator(PG_FUNCTION_ARGS); Datum fmgr_sql_validator(PG_FUNCTION_ARGS); --- 33,38 ---- *************** *** 317,331 **** } /* ! * checkretval() -- check return value of a list of sql parse trees. * * The return value of a sql function is the value returned by ! * the final query in the function. We do some ad-hoc define-time ! * type checking here to be sure that the user is returning the ! * type he claims. */ ! static void ! checkretval(Oid rettype, char fn_typtype, List *queryTreeList) { Query *parse; int cmd; --- 316,335 ---- } /* ! * check_sql_fn_retval() -- check return value of a list of sql parse trees. * * The return value of a sql function is the value returned by ! * the final query in the function. We do some ad-hoc type checking here ! * to be sure that the user is returning the type he claims. ! * ! * This is normally applied during function definition, but in the case ! * of a function with polymorphic arguments, we instead apply it during ! * function execution startup. The rettype is then the actual resolved ! * output type of the function, rather than the declared type. (Therefore, ! * we should never see ANYARRAY or ANYELEMENT as rettype.) */ ! void ! check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList) { Query *parse; int cmd; *************** *** 472,478 **** relation_close(reln, AccessShareLock); } ! else if (fn_typtype == 'p' && rettype == RECORDOID) { /* Shouldn't have a typerelid */ Assert(typerelid == InvalidOid); --- 476,482 ---- relation_close(reln, AccessShareLock); } ! else if (rettype == RECORDOID) { /* Shouldn't have a typerelid */ Assert(typerelid == InvalidOid); *************** *** 482,487 **** --- 486,499 ---- * tuple. */ } + else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) + { + /* + * This should already have been caught ... + */ + elog(ERROR, "functions returning ANYARRAY or ANYELEMENT must " \ + "have at least one argument of either type"); + } else elog(ERROR, "return type %s is not supported for SQL functions", format_type_be(rettype)); *************** *** 505,511 **** Datum tmp; char *prosrc; ! tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup of function %u failed", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); --- 517,525 ---- Datum tmp; char *prosrc; ! tuple = SearchSysCache(PROCOID, ! ObjectIdGetDatum(funcoid), ! 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup of function %u failed", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); *************** *** 544,550 **** char *prosrc; char *probin; ! tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup of function %u failed", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); --- 558,566 ---- char *prosrc; char *probin; ! tuple = SearchSysCache(PROCOID, ! ObjectIdGetDatum(funcoid), ! 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup of function %u failed", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); *************** *** 585,622 **** Datum tmp; char *prosrc; char functyptype; int i; ! tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup of function %u failed", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); functyptype = get_typtype(proc->prorettype); ! /* Disallow pseudotypes in arguments and result */ ! /* except that return type can be RECORD or VOID */ if (functyptype == 'p' && proc->prorettype != RECORDOID && ! proc->prorettype != VOIDOID) elog(ERROR, "SQL functions cannot return type %s", format_type_be(proc->prorettype)); for (i = 0; i < proc->pronargs; i++) { if (get_typtype(proc->proargtypes[i]) == 'p') ! elog(ERROR, "SQL functions cannot have arguments of type %s", ! format_type_be(proc->proargtypes[i])); } ! tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull); ! if (isnull) ! elog(ERROR, "null prosrc"); ! ! prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp)); ! ! querytree_list = pg_parse_and_rewrite(prosrc, proc->proargtypes, proc->pronargs); ! checkretval(proc->prorettype, functyptype, querytree_list); ReleaseSysCache(tuple); --- 601,662 ---- Datum tmp; char *prosrc; char functyptype; + bool haspolyarg; int i; ! tuple = SearchSysCache(PROCOID, ! ObjectIdGetDatum(funcoid), ! 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup of function %u failed", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); functyptype = get_typtype(proc->prorettype); ! /* Disallow pseudotype result */ ! /* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */ if (functyptype == 'p' && proc->prorettype != RECORDOID && ! proc->prorettype != VOIDOID && ! proc->prorettype != ANYARRAYOID && ! proc->prorettype != ANYELEMENTOID) elog(ERROR, "SQL functions cannot return type %s", format_type_be(proc->prorettype)); + /* Disallow pseudotypes in arguments */ + /* except for ANYARRAY or ANYELEMENT */ + haspolyarg = false; for (i = 0; i < proc->pronargs; i++) { if (get_typtype(proc->proargtypes[i]) == 'p') ! { ! if (proc->proargtypes[i] == ANYARRAYOID || ! proc->proargtypes[i] == ANYELEMENTOID) ! haspolyarg = true; ! else ! elog(ERROR, "SQL functions cannot have arguments of type %s", ! format_type_be(proc->proargtypes[i])); ! } } ! /* ! * We can't precheck the function definition if there are any polymorphic ! * input types, because actual datatypes of expression results will be ! * unresolvable. The check will be done at runtime instead. ! */ ! if (!haspolyarg) ! { ! tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull); ! if (isnull) ! elog(ERROR, "null prosrc"); ! ! prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp)); ! ! querytree_list = pg_parse_and_rewrite(prosrc, ! proc->proargtypes, ! proc->pronargs); ! check_sql_fn_retval(proc->prorettype, functyptype, querytree_list); ! } ReleaseSysCache(tuple); *** src/backend/executor/functions.c.orig Thu Jun 12 13:29:26 2003 --- src/backend/executor/functions.c Mon Jun 30 19:46:17 2003 *************** *** 24,29 **** --- 24,30 ---- #include "tcop/tcopprot.h" #include "tcop/utility.h" #include "utils/builtins.h" + #include "utils/lsyscache.h" #include "utils/syscache.h" *************** *** 76,82 **** /* non-export function prototypes */ static execution_state *init_execution_state(char *src, ! Oid *argOidVect, int nargs); static void init_sql_fcache(FmgrInfo *finfo); static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache); static TupleTableSlot *postquel_getnext(execution_state *es); --- 77,84 ---- /* non-export function prototypes */ static execution_state *init_execution_state(char *src, ! Oid *argOidVect, int nargs, ! Oid rettype, bool haspolyarg); static void init_sql_fcache(FmgrInfo *finfo); static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache); static TupleTableSlot *postquel_getnext(execution_state *es); *************** *** 90,96 **** static execution_state * ! init_execution_state(char *src, Oid *argOidVect, int nargs) { execution_state *firstes; execution_state *preves; --- 92,99 ---- static execution_state * ! init_execution_state(char *src, Oid *argOidVect, int nargs, ! Oid rettype, bool haspolyarg) { execution_state *firstes; execution_state *preves; *************** *** 99,104 **** --- 102,114 ---- queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs); + /* + * If the function has any arguments declared as polymorphic types, + * then it wasn't type-checked at definition time; must do so now. + */ + if (haspolyarg) + check_sql_fn_retval(rettype, get_typtype(rettype), queryTree_list); + firstes = NULL; preves = NULL; *************** *** 133,149 **** --- 143,163 ---- init_sql_fcache(FmgrInfo *finfo) { Oid foid = finfo->fn_oid; + Oid rettype; HeapTuple procedureTuple; HeapTuple typeTuple; Form_pg_proc procedureStruct; Form_pg_type typeStruct; SQLFunctionCachePtr fcache; Oid *argOidVect; + bool haspolyarg; char *src; int nargs; Datum tmp; bool isNull; + fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache)); + /* * get the procedure tuple corresponding to the given function Oid */ *************** *** 153,182 **** if (!HeapTupleIsValid(procedureTuple)) elog(ERROR, "init_sql_fcache: Cache lookup failed for procedure %u", foid); - procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); /* ! * get the return type from the procedure tuple */ typeTuple = SearchSysCache(TYPEOID, ! ObjectIdGetDatum(procedureStruct->prorettype), 0, 0, 0); if (!HeapTupleIsValid(typeTuple)) elog(ERROR, "init_sql_fcache: Cache lookup failed for type %u", ! procedureStruct->prorettype); ! typeStruct = (Form_pg_type) GETSTRUCT(typeTuple); - fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache)); - /* * get the type length and by-value flag from the type tuple */ fcache->typlen = typeStruct->typlen; ! if (typeStruct->typtype != 'c' && ! procedureStruct->prorettype != RECORDOID) { /* The return type is not a composite type, so just use byval */ fcache->typbyval = typeStruct->typbyval; --- 167,203 ---- if (!HeapTupleIsValid(procedureTuple)) elog(ERROR, "init_sql_fcache: Cache lookup failed for procedure %u", foid); procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); /* ! * get the result type from the procedure tuple, and check for ! * polymorphic result type; if so, find out the actual result type. */ + rettype = procedureStruct->prorettype; + + if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) + { + rettype = get_fn_expr_rettype(finfo); + if (rettype == InvalidOid) + elog(ERROR, "could not determine actual result type for function declared %s", + format_type_be(procedureStruct->prorettype)); + } + + /* Now look up the actual result type */ typeTuple = SearchSysCache(TYPEOID, ! ObjectIdGetDatum(rettype), 0, 0, 0); if (!HeapTupleIsValid(typeTuple)) elog(ERROR, "init_sql_fcache: Cache lookup failed for type %u", ! rettype); typeStruct = (Form_pg_type) GETSTRUCT(typeTuple); /* * get the type length and by-value flag from the type tuple */ fcache->typlen = typeStruct->typlen; ! if (typeStruct->typtype != 'c' && rettype != RECORDOID) { /* The return type is not a composite type, so just use byval */ fcache->typbyval = typeStruct->typbyval; *************** *** 205,221 **** fcache->funcSlot = NULL; /* ! * Parse and plan the queries. We need the argument info to pass * to the parser. */ nargs = procedureStruct->pronargs; if (nargs > 0) { argOidVect = (Oid *) palloc(nargs * sizeof(Oid)); memcpy(argOidVect, procedureStruct->proargtypes, nargs * sizeof(Oid)); } else argOidVect = (Oid *) NULL; --- 226,260 ---- fcache->funcSlot = NULL; /* ! * Parse and plan the queries. We need the argument type info to pass * to the parser. */ nargs = procedureStruct->pronargs; + haspolyarg = false; if (nargs > 0) { + int argnum; + argOidVect = (Oid *) palloc(nargs * sizeof(Oid)); memcpy(argOidVect, procedureStruct->proargtypes, nargs * sizeof(Oid)); + /* Resolve any polymorphic argument types */ + for (argnum = 0; argnum < nargs; argnum++) + { + Oid argtype = argOidVect[argnum]; + + if (argtype == ANYARRAYOID || argtype == ANYELEMENTOID) + { + argtype = get_fn_expr_argtype(finfo, argnum); + if (argtype == InvalidOid) + elog(ERROR, "could not determine actual type of argument declared %s", + format_type_be(argOidVect[argnum])); + argOidVect[argnum] = argtype; + haspolyarg = true; + } + } } else argOidVect = (Oid *) NULL; *************** *** 229,235 **** foid); src = DatumGetCString(DirectFunctionCall1(textout, tmp)); ! fcache->func_state = init_execution_state(src, argOidVect, nargs); pfree(src); --- 268,275 ---- foid); src = DatumGetCString(DirectFunctionCall1(textout, tmp)); ! fcache->func_state = init_execution_state(src, argOidVect, nargs, ! rettype, haspolyarg); pfree(src); *** src/backend/optimizer/util/clauses.c.orig Sat Jun 28 20:33:43 2003 --- src/backend/optimizer/util/clauses.c Mon Jun 30 18:47:38 2003 *************** *** 1731,1736 **** --- 1731,1737 ---- int *usecounts; List *arg; int i; + int j; /* * Forget it if the function is not SQL-language or has other *************** *** 1742,1752 **** funcform->pronargs != length(args)) return NULL; ! /* Forget it if declared return type is tuple or void */ result_typtype = get_typtype(funcform->prorettype); if (result_typtype != 'b' && result_typtype != 'd') return NULL; /* Check for recursive function, and give up trying to expand if so */ if (oidMember(funcid, active_fns)) --- 1743,1761 ---- funcform->pronargs != length(args)) return NULL; ! /* Forget it if declared return type is not base or domain */ result_typtype = get_typtype(funcform->prorettype); if (result_typtype != 'b' && result_typtype != 'd') return NULL; + + /* Forget it if any declared argument type is polymorphic */ + for (j = 0; j < funcform->pronargs; j++) + { + if (funcform->proargtypes[j] == ANYARRAYOID || + funcform->proargtypes[j] == ANYELEMENTOID) + return NULL; + } /* Check for recursive function, and give up trying to expand if so */ if (oidMember(funcid, active_fns)) *** src/backend/utils/adt/array_userfuncs.c.orig Thu Jun 26 20:33:25 2003 --- src/backend/utils/adt/array_userfuncs.c Mon Jun 30 18:40:03 2003 *************** *** 37,44 **** int16 typlen; bool typbyval; char typalign; ! Oid arg0_typeid = get_fn_expr_argtype(fcinfo, 0); ! Oid arg1_typeid = get_fn_expr_argtype(fcinfo, 1); Oid arg0_elemid; Oid arg1_elemid; ArrayMetaState *my_extra; --- 37,44 ---- int16 typlen; bool typbyval; char typalign; ! Oid arg0_typeid = get_fn_expr_argtype(fcinfo->flinfo, 0); ! Oid arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1); Oid arg0_elemid; Oid arg1_elemid; ArrayMetaState *my_extra; *** src/backend/utils/adt/arrayfuncs.c.orig Thu Jun 26 20:33:25 2003 --- src/backend/utils/adt/arrayfuncs.c Mon Jun 30 18:40:03 2003 *************** *** 2792,2798 **** if (my_extra->srctype != src_elem_type) { ! Oid tgt_type = get_fn_expr_rettype(fcinfo); Oid tgt_elem_type; Oid funcId; --- 2792,2798 ---- if (my_extra->srctype != src_elem_type) { ! Oid tgt_type = get_fn_expr_rettype(fmgr_info); Oid tgt_elem_type; Oid funcId; *** src/backend/utils/fmgr/fmgr.c.orig Sat Jun 28 20:33:44 2003 --- src/backend/utils/fmgr/fmgr.c Mon Jun 30 18:39:50 2003 *************** *** 1616,1631 **** /*------------------------------------------------------------------------- * Support routines for extracting info from fn_expr parse tree *------------------------------------------------------------------------- */ /* ! * Get the OID of the function return type * * Returns InvalidOid if information is not available */ Oid ! get_fn_expr_rettype(FunctionCallInfo fcinfo) { Node *expr; --- 1616,1634 ---- /*------------------------------------------------------------------------- * Support routines for extracting info from fn_expr parse tree + * + * These are needed by polymorphic functions, which accept multiple possible + * input types and need help from the parser to know what they've got. *------------------------------------------------------------------------- */ /* ! * Get the actual type OID of the function return type * * Returns InvalidOid if information is not available */ Oid ! get_fn_expr_rettype(FmgrInfo *flinfo) { Node *expr; *************** *** 1633,1653 **** * can't return anything useful if we have no FmgrInfo or if * its fn_expr node has not been initialized */ ! if (!fcinfo || !fcinfo->flinfo || !fcinfo->flinfo->fn_expr) return InvalidOid; ! expr = fcinfo->flinfo->fn_expr; return exprType(expr); } /* ! * Get the type OID of a specific function argument (counting from 0) * * Returns InvalidOid if information is not available */ Oid ! get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum) { Node *expr; List *args; --- 1636,1656 ---- * can't return anything useful if we have no FmgrInfo or if * its fn_expr node has not been initialized */ ! if (!flinfo || !flinfo->fn_expr) return InvalidOid; ! expr = flinfo->fn_expr; return exprType(expr); } /* ! * Get the actual type OID of a specific function argument (counting from 0) * * Returns InvalidOid if information is not available */ Oid ! get_fn_expr_argtype(FmgrInfo *flinfo, int argnum) { Node *expr; List *args; *************** *** 1657,1666 **** * can't return anything useful if we have no FmgrInfo or if * its fn_expr node has not been initialized */ ! if (!fcinfo || !fcinfo->flinfo || !fcinfo->flinfo->fn_expr) return InvalidOid; ! expr = fcinfo->flinfo->fn_expr; if (IsA(expr, FuncExpr)) args = ((FuncExpr *) expr)->args; --- 1660,1669 ---- * can't return anything useful if we have no FmgrInfo or if * its fn_expr node has not been initialized */ ! if (!flinfo || !flinfo->fn_expr) return InvalidOid; ! expr = flinfo->fn_expr; if (IsA(expr, FuncExpr)) args = ((FuncExpr *) expr)->args; *** src/include/catalog/pg_proc.h.orig Thu Jun 26 20:33:25 2003 --- src/include/catalog/pg_proc.h Mon Jun 30 19:42:59 2003 *************** *** 3438,3441 **** --- 3438,3444 ---- int parameterCount, const Oid *parameterTypes); + extern void check_sql_fn_retval(Oid rettype, char fn_typtype, + List *queryTreeList); + #endif /* PG_PROC_H */ *** src/include/fmgr.h.orig Thu Jun 26 10:18:56 2003 --- src/include/fmgr.h Mon Jun 30 18:39:37 2003 *************** *** 378,385 **** */ extern Pg_finfo_record *fetch_finfo_record(void *filehandle, char *funcname); extern Oid fmgr_internal_function(const char *proname); ! extern Oid get_fn_expr_rettype(FunctionCallInfo fcinfo); ! extern Oid get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum); /* * Routines in dfmgr.c --- 378,385 ---- */ extern Pg_finfo_record *fetch_finfo_record(void *filehandle, char *funcname); extern Oid fmgr_internal_function(const char *proname); ! extern Oid get_fn_expr_rettype(FmgrInfo *flinfo); ! extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum); /* * Routines in dfmgr.c
Tom Lane wrote: > What I've done instead is not to weaken type checking, but simply to > postpone all checking of the body of a SQL function to runtime if it > has any polymorphic arguments. At runtime, we know the actual types > for the arguments, and we know the actual assigned result type, and > then we can run the normal checking operations without any problem. As usual, big improvement in what I submitted. Thanks. > Applied patch attached, just FYI. (It still needs documentation > updates, which I trust you will supply later.) Yup, you have my gold plated IOU on the doc cleanup for all this stuff. One note; this change > Oid > ! get_fn_expr_rettype(FunctionCallInfo fcinfo) > { [snip] > Oid > ! get_fn_expr_rettype(FmgrInfo *flinfo) > { is a good example why some things, particularly PLs, are better off being in the main source tree rather than on gborg (or someplace else). PL/R uses get_fn_expr_rettype() and get_fn_expr_argtype(), so it's now broken as of CVS tip :( I know the license issue is the primary reason why PL/R is not in the main source tree, but I bring it up because I think the tendency to push things out and over to gborg has been too strong lately. Joe
Joe Conway <mail@joeconway.com> writes: > One note; this change >> ! get_fn_expr_rettype(FunctionCallInfo fcinfo) >> to >> ! get_fn_expr_rettype(FmgrInfo *flinfo) > is a good example why some things, particularly PLs, are better off > being in the main source tree rather than on gborg (or someplace else). > PL/R uses get_fn_expr_rettype() and get_fn_expr_argtype(), so it's now > broken as of CVS tip :( Sorry about that. I suspected you had some calls I didn't know about, but there wasn't much I could do about 'em; and I wanted to correct the function signatures before anything else started to depend on them. regards, tom lane