mirror of
https://github.com/postgres/postgres.git
synced 2025-07-05 07:21:24 +03:00
Allow polymorphic aggregates to have non-polymorphic state data types.
Before 9.4, such an aggregate couldn't be declared, because its final function would have to have polymorphic result type but no polymorphic argument, which CREATE FUNCTION would quite properly reject. The ordered-set-aggregate patch found a workaround: allow the final function to be declared as accepting additional dummy arguments that have types matching the aggregate's regular input arguments. However, we failed to notice that this problem applies just as much to regular aggregates, despite the fact that we had a built-in regular aggregate array_agg() that was known to be undeclarable in SQL because its final function had an illegal signature. So what we should have done, and what this patch does, is to decouple the extra-dummy-arguments behavior from ordered-set aggregates and make it generally available for all aggregate declarations. We have to put this into 9.4 rather than waiting till later because it slightly alters the rules for declaring ordered-set aggregates. The patch turned out a bit bigger than I'd hoped because it proved necessary to record the extra-arguments option in a new pg_aggregate column. I'd thought we could just look at the final function's pronargs at runtime, but that didn't work well for variadic final functions. It's probably just as well though, because it simplifies life for pg_dump to record the option explicitly. While at it, fix array_agg() to have a valid final-function signature, and add an opr_sanity test to notice future deviations from polymorphic consistency. I also marked the percentile_cont() aggregates as not needing extra arguments, since they don't.
This commit is contained in:
@ -60,6 +60,8 @@ AggregateCreate(const char *aggName,
|
||||
List *aggmtransfnName,
|
||||
List *aggminvtransfnName,
|
||||
List *aggmfinalfnName,
|
||||
bool finalfnExtraArgs,
|
||||
bool mfinalfnExtraArgs,
|
||||
List *aggsortopName,
|
||||
Oid aggTransType,
|
||||
int32 aggTransSpace,
|
||||
@ -344,48 +346,46 @@ AggregateCreate(const char *aggName,
|
||||
ReleaseSysCache(tup);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up fnArgs for looking up finalfn(s)
|
||||
*
|
||||
* For ordinary aggs, the finalfn just takes the transtype. For
|
||||
* ordered-set aggs, it takes the transtype plus all args. (The
|
||||
* aggregated args are useless at runtime, and are actually passed as
|
||||
* NULLs, but we may need them in the function signature to allow
|
||||
* resolution of a polymorphic agg's result type.)
|
||||
*/
|
||||
fnArgs[0] = aggTransType;
|
||||
if (AGGKIND_IS_ORDERED_SET(aggKind))
|
||||
{
|
||||
nargs_finalfn = numArgs + 1;
|
||||
memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
|
||||
}
|
||||
else
|
||||
{
|
||||
nargs_finalfn = 1;
|
||||
/* variadic-ness of the aggregate doesn't affect finalfn */
|
||||
variadicArgType = InvalidOid;
|
||||
}
|
||||
|
||||
/* handle finalfn, if supplied */
|
||||
if (aggfinalfnName)
|
||||
{
|
||||
/*
|
||||
* If finalfnExtraArgs is specified, the transfn takes the transtype
|
||||
* plus all args; otherwise, it just takes the transtype plus any
|
||||
* direct args. (Non-direct args are useless at runtime, and are
|
||||
* actually passed as NULLs, but we may need them in the function
|
||||
* signature to allow resolution of a polymorphic agg's result type.)
|
||||
*/
|
||||
Oid ffnVariadicArgType = variadicArgType;
|
||||
|
||||
fnArgs[0] = aggTransType;
|
||||
memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
|
||||
if (finalfnExtraArgs)
|
||||
nargs_finalfn = numArgs + 1;
|
||||
else
|
||||
{
|
||||
nargs_finalfn = numDirectArgs + 1;
|
||||
if (numDirectArgs < numArgs)
|
||||
{
|
||||
/* variadic argument doesn't affect finalfn */
|
||||
ffnVariadicArgType = InvalidOid;
|
||||
}
|
||||
}
|
||||
|
||||
finalfn = lookup_agg_function(aggfinalfnName, nargs_finalfn,
|
||||
fnArgs, variadicArgType,
|
||||
fnArgs, ffnVariadicArgType,
|
||||
&finaltype);
|
||||
|
||||
/*
|
||||
* The finalfn of an ordered-set agg will certainly be passed at least
|
||||
* one null argument, so complain if it's strict. Nothing bad would
|
||||
* happen at runtime (you'd just get a null result), but it's surely
|
||||
* not what the user wants, so let's complain now.
|
||||
*
|
||||
* Note: it's likely that a strict transfn would also be a mistake,
|
||||
* but the case isn't quite so airtight, so we let that pass.
|
||||
* When finalfnExtraArgs is specified, the finalfn will certainly be
|
||||
* passed at least one null argument, so complain if it's strict.
|
||||
* Nothing bad would happen at runtime (you'd just get a null result),
|
||||
* but it's surely not what the user wants, so let's complain now.
|
||||
*/
|
||||
if (AGGKIND_IS_ORDERED_SET(aggKind) && func_strict(finalfn))
|
||||
if (finalfnExtraArgs && func_strict(finalfn))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||
errmsg("final function of an ordered-set aggregate must not be declared STRICT")));
|
||||
errmsg("final function with extra arguments must not be declared STRICT")));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -434,21 +434,34 @@ AggregateCreate(const char *aggName,
|
||||
if (aggmfinalfnName)
|
||||
{
|
||||
/*
|
||||
* The arguments are the same as for the regular finalfn, except
|
||||
* that the transition data type might be different. So re-use
|
||||
* the fnArgs values set up above, except for that one.
|
||||
* The arguments are figured the same way as for the regular
|
||||
* finalfn, but using aggmTransType and mfinalfnExtraArgs.
|
||||
*/
|
||||
Oid ffnVariadicArgType = variadicArgType;
|
||||
|
||||
fnArgs[0] = aggmTransType;
|
||||
memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid));
|
||||
if (mfinalfnExtraArgs)
|
||||
nargs_finalfn = numArgs + 1;
|
||||
else
|
||||
{
|
||||
nargs_finalfn = numDirectArgs + 1;
|
||||
if (numDirectArgs < numArgs)
|
||||
{
|
||||
/* variadic argument doesn't affect finalfn */
|
||||
ffnVariadicArgType = InvalidOid;
|
||||
}
|
||||
}
|
||||
|
||||
mfinalfn = lookup_agg_function(aggmfinalfnName, nargs_finalfn,
|
||||
fnArgs, variadicArgType,
|
||||
fnArgs, ffnVariadicArgType,
|
||||
&rettype);
|
||||
|
||||
/* As above, check strictness if it's an ordered-set agg */
|
||||
if (AGGKIND_IS_ORDERED_SET(aggKind) && func_strict(mfinalfn))
|
||||
/* As above, check strictness if mfinalfnExtraArgs is given */
|
||||
if (mfinalfnExtraArgs && func_strict(mfinalfn))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||
errmsg("final function of an ordered-set aggregate must not be declared STRICT")));
|
||||
errmsg("final function with extra arguments must not be declared STRICT")));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -554,6 +567,8 @@ AggregateCreate(const char *aggName,
|
||||
values[Anum_pg_aggregate_aggmtransfn - 1] = ObjectIdGetDatum(mtransfn);
|
||||
values[Anum_pg_aggregate_aggminvtransfn - 1] = ObjectIdGetDatum(minvtransfn);
|
||||
values[Anum_pg_aggregate_aggmfinalfn - 1] = ObjectIdGetDatum(mfinalfn);
|
||||
values[Anum_pg_aggregate_aggfinalextra - 1] = BoolGetDatum(finalfnExtraArgs);
|
||||
values[Anum_pg_aggregate_aggmfinalextra - 1] = BoolGetDatum(mfinalfnExtraArgs);
|
||||
values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop);
|
||||
values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
|
||||
values[Anum_pg_aggregate_aggtransspace - 1] = Int32GetDatum(aggTransSpace);
|
||||
|
@ -64,6 +64,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
|
||||
List *mtransfuncName = NIL;
|
||||
List *minvtransfuncName = NIL;
|
||||
List *mfinalfuncName = NIL;
|
||||
bool finalfuncExtraArgs = false;
|
||||
bool mfinalfuncExtraArgs = false;
|
||||
List *sortoperatorName = NIL;
|
||||
TypeName *baseType = NULL;
|
||||
TypeName *transType = NULL;
|
||||
@ -128,6 +130,10 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
|
||||
minvtransfuncName = defGetQualifiedName(defel);
|
||||
else if (pg_strcasecmp(defel->defname, "mfinalfunc") == 0)
|
||||
mfinalfuncName = defGetQualifiedName(defel);
|
||||
else if (pg_strcasecmp(defel->defname, "finalfunc_extra") == 0)
|
||||
finalfuncExtraArgs = defGetBoolean(defel);
|
||||
else if (pg_strcasecmp(defel->defname, "mfinalfunc_extra") == 0)
|
||||
mfinalfuncExtraArgs = defGetBoolean(defel);
|
||||
else if (pg_strcasecmp(defel->defname, "sortop") == 0)
|
||||
sortoperatorName = defGetQualifiedName(defel);
|
||||
else if (pg_strcasecmp(defel->defname, "basetype") == 0)
|
||||
@ -380,6 +386,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
|
||||
mtransfuncName, /* fwd trans function name */
|
||||
minvtransfuncName, /* inv trans function name */
|
||||
mfinalfuncName, /* final function name */
|
||||
finalfuncExtraArgs,
|
||||
mfinalfuncExtraArgs,
|
||||
sortoperatorName, /* sort operator name */
|
||||
transTypeId, /* transition data type */
|
||||
transSpace, /* transition space */
|
||||
|
@ -37,11 +37,12 @@
|
||||
*
|
||||
* Ordered-set aggregates are treated specially in one other way: we
|
||||
* evaluate any "direct" arguments and pass them to the finalfunc along
|
||||
* with the transition value. In addition, NULL placeholders are
|
||||
* provided to match the remaining finalfunc arguments, which correspond
|
||||
* to the aggregated expressions. (These arguments have no use at
|
||||
* runtime, but may be needed to allow resolution of a polymorphic
|
||||
* aggregate's result type.)
|
||||
* with the transition value.
|
||||
*
|
||||
* A finalfunc can have additional arguments beyond the transvalue and
|
||||
* any "direct" arguments, corresponding to the input arguments of the
|
||||
* aggregate. These are always just passed as NULL. Such arguments may be
|
||||
* needed to allow resolution of a polymorphic aggregate's result type.
|
||||
*
|
||||
* We compute aggregate input expressions and run the transition functions
|
||||
* in a temporary econtext (aggstate->tmpcontext). This is reset at
|
||||
@ -151,6 +152,14 @@ typedef struct AggStatePerAggData
|
||||
*/
|
||||
int numTransInputs;
|
||||
|
||||
/*
|
||||
* Number of arguments to pass to the finalfn. This is always at least 1
|
||||
* (the transition state value) plus any ordered-set direct args. If the
|
||||
* finalfn wants extra args then we pass nulls corresponding to the
|
||||
* aggregated input columns.
|
||||
*/
|
||||
int numFinalArgs;
|
||||
|
||||
/* Oids of transfer functions */
|
||||
Oid transfn_oid;
|
||||
Oid finalfn_oid; /* may be InvalidOid */
|
||||
@ -797,6 +806,8 @@ finalize_aggregate(AggState *aggstate,
|
||||
/*
|
||||
* Evaluate any direct arguments. We do this even if there's no finalfn
|
||||
* (which is unlikely anyway), so that side-effects happen as expected.
|
||||
* The direct arguments go into arg positions 1 and up, leaving position 0
|
||||
* for the transition state value.
|
||||
*/
|
||||
i = 1;
|
||||
foreach(lc, peraggstate->aggrefstate->aggdirectargs)
|
||||
@ -816,19 +827,7 @@ finalize_aggregate(AggState *aggstate,
|
||||
*/
|
||||
if (OidIsValid(peraggstate->finalfn_oid))
|
||||
{
|
||||
int numFinalArgs;
|
||||
|
||||
/*
|
||||
* Identify number of arguments being passed to the finalfn. For a
|
||||
* plain agg it's just one (the transition state value). For
|
||||
* ordered-set aggs we also pass the direct argument(s), plus nulls
|
||||
* corresponding to the aggregate-input columns.
|
||||
*/
|
||||
if (AGGKIND_IS_ORDERED_SET(peraggstate->aggref->aggkind))
|
||||
numFinalArgs = peraggstate->numArguments + 1;
|
||||
else
|
||||
numFinalArgs = 1;
|
||||
Assert(i <= numFinalArgs);
|
||||
int numFinalArgs = peraggstate->numFinalArgs;
|
||||
|
||||
/* set up aggstate->curperagg for AggGetAggref() */
|
||||
aggstate->curperagg = peraggstate;
|
||||
@ -844,12 +843,11 @@ finalize_aggregate(AggState *aggstate,
|
||||
anynull |= pergroupstate->transValueIsNull;
|
||||
|
||||
/* Fill any remaining argument positions with nulls */
|
||||
while (i < numFinalArgs)
|
||||
for (; i < numFinalArgs; i++)
|
||||
{
|
||||
fcinfo.arg[i] = (Datum) 0;
|
||||
fcinfo.argnull[i] = true;
|
||||
anynull = true;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (fcinfo.flinfo->fn_strict && anynull)
|
||||
@ -1776,12 +1774,18 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
|
||||
numInputs = list_length(aggref->args);
|
||||
peraggstate->numInputs = numInputs;
|
||||
|
||||
/* Detect how many columns to pass to the transfn */
|
||||
/* Detect how many arguments to pass to the transfn */
|
||||
if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
|
||||
peraggstate->numTransInputs = numInputs;
|
||||
else
|
||||
peraggstate->numTransInputs = numArguments;
|
||||
|
||||
/* Detect how many arguments to pass to the finalfn */
|
||||
if (aggform->aggfinalextra)
|
||||
peraggstate->numFinalArgs = numArguments + 1;
|
||||
else
|
||||
peraggstate->numFinalArgs = numDirectArgs + 1;
|
||||
|
||||
/* resolve actual type of transition state, if polymorphic */
|
||||
aggtranstype = resolve_aggregate_transtype(aggref->aggfnoid,
|
||||
aggform->aggtranstype,
|
||||
@ -1792,7 +1796,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
|
||||
build_aggregate_fnexprs(inputTypes,
|
||||
numArguments,
|
||||
numDirectArgs,
|
||||
AGGKIND_IS_ORDERED_SET(aggref->aggkind),
|
||||
peraggstate->numFinalArgs,
|
||||
aggref->aggvariadic,
|
||||
aggtranstype,
|
||||
aggref->aggtype,
|
||||
|
@ -116,6 +116,8 @@ typedef struct WindowStatePerAggData
|
||||
FmgrInfo invtransfn;
|
||||
FmgrInfo finalfn;
|
||||
|
||||
int numFinalArgs; /* number of arguments to pass to finalfn */
|
||||
|
||||
/*
|
||||
* initial value from pg_aggregate entry
|
||||
*/
|
||||
@ -557,14 +559,28 @@ finalize_windowaggregate(WindowAggState *winstate,
|
||||
*/
|
||||
if (OidIsValid(peraggstate->finalfn_oid))
|
||||
{
|
||||
int numFinalArgs = peraggstate->numFinalArgs;
|
||||
FunctionCallInfoData fcinfo;
|
||||
bool anynull;
|
||||
int i;
|
||||
|
||||
InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1,
|
||||
InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn),
|
||||
numFinalArgs,
|
||||
perfuncstate->winCollation,
|
||||
(void *) winstate, NULL);
|
||||
fcinfo.arg[0] = peraggstate->transValue;
|
||||
fcinfo.argnull[0] = peraggstate->transValueIsNull;
|
||||
if (fcinfo.flinfo->fn_strict && peraggstate->transValueIsNull)
|
||||
anynull = peraggstate->transValueIsNull;
|
||||
|
||||
/* Fill any remaining argument positions with nulls */
|
||||
for (i = 1; i < numFinalArgs; i++)
|
||||
{
|
||||
fcinfo.arg[i] = (Datum) 0;
|
||||
fcinfo.argnull[i] = true;
|
||||
anynull = true;
|
||||
}
|
||||
|
||||
if (fcinfo.flinfo->fn_strict && anynull)
|
||||
{
|
||||
/* don't call a strict function with NULL inputs */
|
||||
*result = (Datum) 0;
|
||||
@ -2089,6 +2105,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
|
||||
Oid transfn_oid,
|
||||
invtransfn_oid,
|
||||
finalfn_oid;
|
||||
bool finalextra;
|
||||
Expr *transfnexpr,
|
||||
*invtransfnexpr,
|
||||
*finalfnexpr;
|
||||
@ -2127,6 +2144,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
|
||||
peraggstate->transfn_oid = transfn_oid = aggform->aggmtransfn;
|
||||
peraggstate->invtransfn_oid = invtransfn_oid = aggform->aggminvtransfn;
|
||||
peraggstate->finalfn_oid = finalfn_oid = aggform->aggmfinalfn;
|
||||
finalextra = aggform->aggmfinalextra;
|
||||
aggtranstype = aggform->aggmtranstype;
|
||||
initvalAttNo = Anum_pg_aggregate_aggminitval;
|
||||
}
|
||||
@ -2135,6 +2153,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
|
||||
peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
|
||||
peraggstate->invtransfn_oid = invtransfn_oid = InvalidOid;
|
||||
peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
|
||||
finalextra = aggform->aggfinalextra;
|
||||
aggtranstype = aggform->aggtranstype;
|
||||
initvalAttNo = Anum_pg_aggregate_agginitval;
|
||||
}
|
||||
@ -2185,6 +2204,12 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
|
||||
}
|
||||
}
|
||||
|
||||
/* Detect how many arguments to pass to the finalfn */
|
||||
if (finalextra)
|
||||
peraggstate->numFinalArgs = numArguments + 1;
|
||||
else
|
||||
peraggstate->numFinalArgs = 1;
|
||||
|
||||
/* resolve actual type of transition state, if polymorphic */
|
||||
aggtranstype = resolve_aggregate_transtype(wfunc->winfnoid,
|
||||
aggtranstype,
|
||||
@ -2195,7 +2220,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
|
||||
build_aggregate_fnexprs(inputTypes,
|
||||
numArguments,
|
||||
0, /* no ordered-set window functions yet */
|
||||
false,
|
||||
peraggstate->numFinalArgs,
|
||||
false, /* no variadic window functions yet */
|
||||
aggtranstype,
|
||||
wfunc->wintype,
|
||||
@ -2207,6 +2232,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
|
||||
&invtransfnexpr,
|
||||
&finalfnexpr);
|
||||
|
||||
/* set up infrastructure for calling the transfn(s) and finalfn */
|
||||
fmgr_info(transfn_oid, &peraggstate->transfn);
|
||||
fmgr_info_set_expr((Node *) transfnexpr, &peraggstate->transfn);
|
||||
|
||||
@ -2222,6 +2248,7 @@ initialize_peragg(WindowAggState *winstate, WindowFunc *wfunc,
|
||||
fmgr_info_set_expr((Node *) finalfnexpr, &peraggstate->finalfn);
|
||||
}
|
||||
|
||||
/* get info about relevant datatypes */
|
||||
get_typlenbyval(wfunc->wintype,
|
||||
&peraggstate->resulttypeLen,
|
||||
&peraggstate->resulttypeByVal);
|
||||
|
@ -1199,7 +1199,7 @@ void
|
||||
build_aggregate_fnexprs(Oid *agg_input_types,
|
||||
int agg_num_inputs,
|
||||
int agg_num_direct_inputs,
|
||||
bool agg_ordered_set,
|
||||
int num_finalfn_inputs,
|
||||
bool agg_variadic,
|
||||
Oid agg_state_type,
|
||||
Oid agg_result_type,
|
||||
@ -1292,19 +1292,17 @@ build_aggregate_fnexprs(Oid *agg_input_types,
|
||||
argp->location = -1;
|
||||
args = list_make1(argp);
|
||||
|
||||
if (agg_ordered_set)
|
||||
/* finalfn may take additional args, which match agg's input types */
|
||||
for (i = 0; i < num_finalfn_inputs - 1; i++)
|
||||
{
|
||||
for (i = 0; i < agg_num_inputs; i++)
|
||||
{
|
||||
argp = makeNode(Param);
|
||||
argp->paramkind = PARAM_EXEC;
|
||||
argp->paramid = -1;
|
||||
argp->paramtype = agg_input_types[i];
|
||||
argp->paramtypmod = -1;
|
||||
argp->paramcollid = agg_input_collation;
|
||||
argp->location = -1;
|
||||
args = lappend(args, argp);
|
||||
}
|
||||
argp = makeNode(Param);
|
||||
argp->paramkind = PARAM_EXEC;
|
||||
argp->paramid = -1;
|
||||
argp->paramtype = agg_input_types[i];
|
||||
argp->paramtypmod = -1;
|
||||
argp->paramcollid = agg_input_collation;
|
||||
argp->location = -1;
|
||||
args = lappend(args, argp);
|
||||
}
|
||||
|
||||
*finalfnexpr = (Expr *) makeFuncExpr(finalfn_oid,
|
||||
|
Reference in New Issue
Block a user