diff options
Diffstat (limited to 'src/backend/catalog/pg_aggregate.c')
-rw-r--r-- | src/backend/catalog/pg_aggregate.c | 914 |
1 files changed, 914 insertions, 0 deletions
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c new file mode 100644 index 0000000..1f63d80 --- /dev/null +++ b/src/backend/catalog/pg_aggregate.c @@ -0,0 +1,914 @@ +/*------------------------------------------------------------------------- + * + * pg_aggregate.c + * routines to support manipulation of the pg_aggregate relation + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/catalog/pg_aggregate.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_language.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "parser/parse_coerce.h" +#include "parser/parse_func.h" +#include "parser/parse_oper.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + + +static Oid lookup_agg_function(List *fnName, int nargs, Oid *input_types, + Oid variadicArgType, + Oid *rettype); + + +/* + * AggregateCreate + */ +ObjectAddress +AggregateCreate(const char *aggName, + Oid aggNamespace, + bool replace, + char aggKind, + int numArgs, + int numDirectArgs, + oidvector *parameterTypes, + Datum allParameterTypes, + Datum parameterModes, + Datum parameterNames, + List *parameterDefaults, + Oid variadicArgType, + List *aggtransfnName, + List *aggfinalfnName, + List *aggcombinefnName, + List *aggserialfnName, + List *aggdeserialfnName, + List *aggmtransfnName, + List *aggminvtransfnName, + List *aggmfinalfnName, + bool finalfnExtraArgs, + bool mfinalfnExtraArgs, + char finalfnModify, + char mfinalfnModify, + List *aggsortopName, + Oid aggTransType, + int32 aggTransSpace, + Oid aggmTransType, + int32 aggmTransSpace, + const char *agginitval, + const char *aggminitval, + char proparallel) +{ + Relation aggdesc; + HeapTuple tup; + HeapTuple oldtup; + bool nulls[Natts_pg_aggregate]; + Datum values[Natts_pg_aggregate]; + bool replaces[Natts_pg_aggregate]; + Form_pg_proc proc; + Oid transfn; + Oid finalfn = InvalidOid; /* can be omitted */ + Oid combinefn = InvalidOid; /* can be omitted */ + Oid serialfn = InvalidOid; /* can be omitted */ + Oid deserialfn = InvalidOid; /* can be omitted */ + Oid mtransfn = InvalidOid; /* can be omitted */ + Oid minvtransfn = InvalidOid; /* can be omitted */ + Oid mfinalfn = InvalidOid; /* can be omitted */ + Oid sortop = InvalidOid; /* can be omitted */ + Oid *aggArgTypes = parameterTypes->values; + bool mtransIsStrict = false; + Oid rettype; + Oid finaltype; + Oid fnArgs[FUNC_MAX_ARGS]; + int nargs_transfn; + int nargs_finalfn; + Oid procOid; + TupleDesc tupDesc; + char *detailmsg; + int i; + ObjectAddress myself, + referenced; + ObjectAddresses *addrs; + AclResult aclresult; + + /* sanity checks (caller should have caught these) */ + if (!aggName) + elog(ERROR, "no aggregate name supplied"); + + if (!aggtransfnName) + elog(ERROR, "aggregate must have a transition function"); + + if (numDirectArgs < 0 || numDirectArgs > numArgs) + elog(ERROR, "incorrect number of direct arguments for aggregate"); + + /* + * Aggregates can have at most FUNC_MAX_ARGS-1 args, else the transfn + * and/or finalfn will be unrepresentable in pg_proc. We must check now + * to protect fixed-size arrays here and possibly in called functions. + */ + if (numArgs < 0 || numArgs > FUNC_MAX_ARGS - 1) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg_plural("aggregates cannot have more than %d argument", + "aggregates cannot have more than %d arguments", + FUNC_MAX_ARGS - 1, + FUNC_MAX_ARGS - 1))); + + /* + * If transtype is polymorphic, must have polymorphic argument also; else + * we will have no way to deduce the actual transtype. + */ + detailmsg = check_valid_polymorphic_signature(aggTransType, + aggArgTypes, + numArgs); + if (detailmsg) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine transition data type"), + errdetail_internal("%s", detailmsg))); + + /* + * Likewise for moving-aggregate transtype, if any + */ + if (OidIsValid(aggmTransType)) + { + detailmsg = check_valid_polymorphic_signature(aggmTransType, + aggArgTypes, + numArgs); + if (detailmsg) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine transition data type"), + errdetail_internal("%s", detailmsg))); + } + + /* + * An ordered-set aggregate that is VARIADIC must be VARIADIC ANY. In + * principle we could support regular variadic types, but it would make + * things much more complicated because we'd have to assemble the correct + * subsets of arguments into array values. Since no standard aggregates + * have use for such a case, we aren't bothering for now. + */ + if (AGGKIND_IS_ORDERED_SET(aggKind) && OidIsValid(variadicArgType) && + variadicArgType != ANYOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("a variadic ordered-set aggregate must use VARIADIC type ANY"))); + + /* + * If it's a hypothetical-set aggregate, there must be at least as many + * direct arguments as aggregated ones, and the last N direct arguments + * must match the aggregated ones in type. (We have to check this again + * when the aggregate is called, in case ANY is involved, but it makes + * sense to reject the aggregate definition now if the declared arg types + * don't match up.) It's unconditionally OK if numDirectArgs == numArgs, + * indicating that the grammar merged identical VARIADIC entries from both + * lists. Otherwise, if the agg is VARIADIC, then we had VARIADIC only on + * the aggregated side, which is not OK. Otherwise, insist on the last N + * parameter types on each side matching exactly. + */ + if (aggKind == AGGKIND_HYPOTHETICAL && + numDirectArgs < numArgs) + { + int numAggregatedArgs = numArgs - numDirectArgs; + + if (OidIsValid(variadicArgType) || + numDirectArgs < numAggregatedArgs || + memcmp(aggArgTypes + (numDirectArgs - numAggregatedArgs), + aggArgTypes + numDirectArgs, + numAggregatedArgs * sizeof(Oid)) != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("a hypothetical-set aggregate must have direct arguments matching its aggregated arguments"))); + } + + /* + * Find the transfn. For ordinary aggs, it takes the transtype plus all + * aggregate arguments. For ordered-set aggs, it takes the transtype plus + * all aggregated args, but not direct args. However, we have to treat + * specially the case where a trailing VARIADIC item is considered to + * cover both direct and aggregated args. + */ + if (AGGKIND_IS_ORDERED_SET(aggKind)) + { + if (numDirectArgs < numArgs) + nargs_transfn = numArgs - numDirectArgs + 1; + else + { + /* special case with VARIADIC last arg */ + Assert(variadicArgType != InvalidOid); + nargs_transfn = 2; + } + fnArgs[0] = aggTransType; + memcpy(fnArgs + 1, aggArgTypes + (numArgs - (nargs_transfn - 1)), + (nargs_transfn - 1) * sizeof(Oid)); + } + else + { + nargs_transfn = numArgs + 1; + fnArgs[0] = aggTransType; + memcpy(fnArgs + 1, aggArgTypes, numArgs * sizeof(Oid)); + } + transfn = lookup_agg_function(aggtransfnName, nargs_transfn, + fnArgs, variadicArgType, + &rettype); + + /* + * Return type of transfn (possibly after refinement by + * enforce_generic_type_consistency, if transtype isn't polymorphic) must + * exactly match declared transtype. + * + * In the non-polymorphic-transtype case, it might be okay to allow a + * rettype that's binary-coercible to transtype, but I'm not quite + * convinced that it's either safe or useful. When transtype is + * polymorphic we *must* demand exact equality. + */ + if (rettype != aggTransType) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("return type of transition function %s is not %s", + NameListToString(aggtransfnName), + format_type_be(aggTransType)))); + + tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(transfn)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for function %u", transfn); + proc = (Form_pg_proc) GETSTRUCT(tup); + + /* + * If the transfn is strict and the initval is NULL, make sure first input + * type and transtype are the same (or at least binary-compatible), so + * that it's OK to use the first input value as the initial transValue. + */ + if (proc->proisstrict && agginitval == NULL) + { + if (numArgs < 1 || + !IsBinaryCoercible(aggArgTypes[0], aggTransType)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type"))); + } + + ReleaseSysCache(tup); + + /* handle moving-aggregate transfn, if supplied */ + if (aggmtransfnName) + { + /* + * The arguments are the same as for the regular transfn, except that + * the transition data type might be different. So re-use the fnArgs + * values set up above, except for that one. + */ + Assert(OidIsValid(aggmTransType)); + fnArgs[0] = aggmTransType; + + mtransfn = lookup_agg_function(aggmtransfnName, nargs_transfn, + fnArgs, variadicArgType, + &rettype); + + /* As above, return type must exactly match declared mtranstype. */ + if (rettype != aggmTransType) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("return type of transition function %s is not %s", + NameListToString(aggmtransfnName), + format_type_be(aggmTransType)))); + + tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(mtransfn)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for function %u", mtransfn); + proc = (Form_pg_proc) GETSTRUCT(tup); + + /* + * If the mtransfn is strict and the minitval is NULL, check first + * input type and mtranstype are binary-compatible. + */ + if (proc->proisstrict && aggminitval == NULL) + { + if (numArgs < 1 || + !IsBinaryCoercible(aggArgTypes[0], aggmTransType)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("must not omit initial value when transition function is strict and transition type is not compatible with input type"))); + } + + /* Remember if mtransfn is strict; we may need this below */ + mtransIsStrict = proc->proisstrict; + + ReleaseSysCache(tup); + } + + /* handle minvtransfn, if supplied */ + if (aggminvtransfnName) + { + /* + * This must have the same number of arguments with the same types as + * the forward transition function, so just re-use the fnArgs data. + */ + Assert(aggmtransfnName); + + minvtransfn = lookup_agg_function(aggminvtransfnName, nargs_transfn, + fnArgs, variadicArgType, + &rettype); + + /* As above, return type must exactly match declared mtranstype. */ + if (rettype != aggmTransType) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("return type of inverse transition function %s is not %s", + NameListToString(aggminvtransfnName), + format_type_be(aggmTransType)))); + + tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(minvtransfn)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for function %u", minvtransfn); + proc = (Form_pg_proc) GETSTRUCT(tup); + + /* + * We require the strictness settings of the forward and inverse + * transition functions to agree. This saves having to handle + * assorted special cases at execution time. + */ + if (proc->proisstrict != mtransIsStrict) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("strictness of aggregate's forward and inverse transition functions must match"))); + + ReleaseSysCache(tup); + } + + /* 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, ffnVariadicArgType, + &finaltype); + + /* + * 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 (finalfnExtraArgs && func_strict(finalfn)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("final function with extra arguments must not be declared STRICT"))); + } + else + { + /* + * If no finalfn, aggregate result type is type of the state value + */ + finaltype = aggTransType; + } + Assert(OidIsValid(finaltype)); + + /* handle the combinefn, if supplied */ + if (aggcombinefnName) + { + Oid combineType; + + /* + * Combine function must have 2 arguments, each of which is the trans + * type. VARIADIC doesn't affect it. + */ + fnArgs[0] = aggTransType; + fnArgs[1] = aggTransType; + + combinefn = lookup_agg_function(aggcombinefnName, 2, + fnArgs, InvalidOid, + &combineType); + + /* Ensure the return type matches the aggregate's trans type */ + if (combineType != aggTransType) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("return type of combine function %s is not %s", + NameListToString(aggcombinefnName), + format_type_be(aggTransType)))); + + /* + * A combine function to combine INTERNAL states must accept nulls and + * ensure that the returned state is in the correct memory context. We + * cannot directly check the latter, but we can check the former. + */ + if (aggTransType == INTERNALOID && func_strict(combinefn)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("combine function with transition type %s must not be declared STRICT", + format_type_be(aggTransType)))); + } + + /* + * Validate the serialization function, if present. + */ + if (aggserialfnName) + { + /* signature is always serialize(internal) returns bytea */ + fnArgs[0] = INTERNALOID; + + serialfn = lookup_agg_function(aggserialfnName, 1, + fnArgs, InvalidOid, + &rettype); + + if (rettype != BYTEAOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("return type of serialization function %s is not %s", + NameListToString(aggserialfnName), + format_type_be(BYTEAOID)))); + } + + /* + * Validate the deserialization function, if present. + */ + if (aggdeserialfnName) + { + /* signature is always deserialize(bytea, internal) returns internal */ + fnArgs[0] = BYTEAOID; + fnArgs[1] = INTERNALOID; /* dummy argument for type safety */ + + deserialfn = lookup_agg_function(aggdeserialfnName, 2, + fnArgs, InvalidOid, + &rettype); + + if (rettype != INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("return type of deserialization function %s is not %s", + NameListToString(aggdeserialfnName), + format_type_be(INTERNALOID)))); + } + + /* + * If finaltype (i.e. aggregate return type) is polymorphic, inputs must + * be polymorphic also, else parser will fail to deduce result type. + * (Note: given the previous test on transtype and inputs, this cannot + * happen, unless someone has snuck a finalfn definition into the catalogs + * that itself violates the rule against polymorphic result with no + * polymorphic input.) + */ + detailmsg = check_valid_polymorphic_signature(finaltype, + aggArgTypes, + numArgs); + if (detailmsg) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot determine result data type"), + errdetail_internal("%s", detailmsg))); + + /* + * Also, the return type can't be INTERNAL unless there's at least one + * INTERNAL argument. This is the same type-safety restriction we enforce + * for regular functions, but at the level of aggregates. We must test + * this explicitly because we allow INTERNAL as the transtype. + */ + detailmsg = check_valid_internal_signature(finaltype, + aggArgTypes, + numArgs); + if (detailmsg) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("unsafe use of pseudo-type \"internal\""), + errdetail_internal("%s", detailmsg))); + + /* + * If a moving-aggregate implementation is supplied, look up its finalfn + * if any, and check that the implied aggregate result type matches the + * plain implementation. + */ + if (OidIsValid(aggmTransType)) + { + /* handle finalfn, if supplied */ + if (aggmfinalfnName) + { + /* + * 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, ffnVariadicArgType, + &rettype); + + /* As above, check strictness if mfinalfnExtraArgs is given */ + if (mfinalfnExtraArgs && func_strict(mfinalfn)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("final function with extra arguments must not be declared STRICT"))); + } + else + { + /* + * If no finalfn, aggregate result type is type of the state value + */ + rettype = aggmTransType; + } + Assert(OidIsValid(rettype)); + if (rettype != finaltype) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("moving-aggregate implementation returns type %s, but plain implementation returns type %s", + format_type_be(rettype), + format_type_be(finaltype)))); + } + + /* handle sortop, if supplied */ + if (aggsortopName) + { + if (numArgs != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("sort operator can only be specified for single-argument aggregates"))); + sortop = LookupOperName(NULL, aggsortopName, + aggArgTypes[0], aggArgTypes[0], + false, -1); + } + + /* + * permission checks on used types + */ + for (i = 0; i < numArgs; i++) + { + aclresult = pg_type_aclcheck(aggArgTypes[i], GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, aggArgTypes[i]); + } + + aclresult = pg_type_aclcheck(aggTransType, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, aggTransType); + + if (OidIsValid(aggmTransType)) + { + aclresult = pg_type_aclcheck(aggmTransType, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, aggmTransType); + } + + aclresult = pg_type_aclcheck(finaltype, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, finaltype); + + + /* + * Everything looks okay. Try to create the pg_proc entry for the + * aggregate. (This could fail if there's already a conflicting entry.) + */ + + myself = ProcedureCreate(aggName, + aggNamespace, + replace, /* maybe replacement */ + false, /* doesn't return a set */ + finaltype, /* returnType */ + GetUserId(), /* proowner */ + INTERNALlanguageId, /* languageObjectId */ + InvalidOid, /* no validator */ + "aggregate_dummy", /* placeholder (no such proc) */ + NULL, /* probin */ + NULL, /* prosqlbody */ + PROKIND_AGGREGATE, + false, /* security invoker (currently not + * definable for agg) */ + false, /* isLeakProof */ + false, /* isStrict (not needed for agg) */ + PROVOLATILE_IMMUTABLE, /* volatility (not needed + * for agg) */ + proparallel, + parameterTypes, /* paramTypes */ + allParameterTypes, /* allParamTypes */ + parameterModes, /* parameterModes */ + parameterNames, /* parameterNames */ + parameterDefaults, /* parameterDefaults */ + PointerGetDatum(NULL), /* trftypes */ + PointerGetDatum(NULL), /* proconfig */ + InvalidOid, /* no prosupport */ + 1, /* procost */ + 0); /* prorows */ + procOid = myself.objectId; + + /* + * Okay to create the pg_aggregate entry. + */ + aggdesc = table_open(AggregateRelationId, RowExclusiveLock); + tupDesc = aggdesc->rd_att; + + /* initialize nulls and values */ + for (i = 0; i < Natts_pg_aggregate; i++) + { + nulls[i] = false; + values[i] = (Datum) NULL; + replaces[i] = true; + } + values[Anum_pg_aggregate_aggfnoid - 1] = ObjectIdGetDatum(procOid); + values[Anum_pg_aggregate_aggkind - 1] = CharGetDatum(aggKind); + values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs); + values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn); + values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn); + values[Anum_pg_aggregate_aggcombinefn - 1] = ObjectIdGetDatum(combinefn); + values[Anum_pg_aggregate_aggserialfn - 1] = ObjectIdGetDatum(serialfn); + values[Anum_pg_aggregate_aggdeserialfn - 1] = ObjectIdGetDatum(deserialfn); + 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_aggfinalmodify - 1] = CharGetDatum(finalfnModify); + values[Anum_pg_aggregate_aggmfinalmodify - 1] = CharGetDatum(mfinalfnModify); + values[Anum_pg_aggregate_aggsortop - 1] = ObjectIdGetDatum(sortop); + values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType); + values[Anum_pg_aggregate_aggtransspace - 1] = Int32GetDatum(aggTransSpace); + values[Anum_pg_aggregate_aggmtranstype - 1] = ObjectIdGetDatum(aggmTransType); + values[Anum_pg_aggregate_aggmtransspace - 1] = Int32GetDatum(aggmTransSpace); + if (agginitval) + values[Anum_pg_aggregate_agginitval - 1] = CStringGetTextDatum(agginitval); + else + nulls[Anum_pg_aggregate_agginitval - 1] = true; + if (aggminitval) + values[Anum_pg_aggregate_aggminitval - 1] = CStringGetTextDatum(aggminitval); + else + nulls[Anum_pg_aggregate_aggminitval - 1] = true; + + if (replace) + oldtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(procOid)); + else + oldtup = NULL; + + if (HeapTupleIsValid(oldtup)) + { + Form_pg_aggregate oldagg = (Form_pg_aggregate) GETSTRUCT(oldtup); + + /* + * If we're replacing an existing entry, we need to validate that + * we're not changing anything that would break callers. Specifically + * we must not change aggkind or aggnumdirectargs, which affect how an + * aggregate call is treated in parse analysis. + */ + if (aggKind != oldagg->aggkind) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change routine kind"), + (oldagg->aggkind == AGGKIND_NORMAL ? + errdetail("\"%s\" is an ordinary aggregate function.", aggName) : + oldagg->aggkind == AGGKIND_ORDERED_SET ? + errdetail("\"%s\" is an ordered-set aggregate.", aggName) : + oldagg->aggkind == AGGKIND_HYPOTHETICAL ? + errdetail("\"%s\" is a hypothetical-set aggregate.", aggName) : + 0))); + if (numDirectArgs != oldagg->aggnumdirectargs) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot change number of direct arguments of an aggregate function"))); + + replaces[Anum_pg_aggregate_aggfnoid - 1] = false; + replaces[Anum_pg_aggregate_aggkind - 1] = false; + replaces[Anum_pg_aggregate_aggnumdirectargs - 1] = false; + + tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces); + CatalogTupleUpdate(aggdesc, &tup->t_self, tup); + ReleaseSysCache(oldtup); + } + else + { + tup = heap_form_tuple(tupDesc, values, nulls); + CatalogTupleInsert(aggdesc, tup); + } + + table_close(aggdesc, RowExclusiveLock); + + /* + * Create dependencies for the aggregate (above and beyond those already + * made by ProcedureCreate). Note: we don't need an explicit dependency + * on aggTransType since we depend on it indirectly through transfn. + * Likewise for aggmTransType using the mtransfn, if it exists. + * + * If we're replacing an existing definition, ProcedureCreate deleted all + * our existing dependencies, so we have to do the same things here either + * way. + */ + + addrs = new_object_addresses(); + + /* Depends on transition function */ + ObjectAddressSet(referenced, ProcedureRelationId, transfn); + add_exact_object_address(&referenced, addrs); + + /* Depends on final function, if any */ + if (OidIsValid(finalfn)) + { + ObjectAddressSet(referenced, ProcedureRelationId, finalfn); + add_exact_object_address(&referenced, addrs); + } + + /* Depends on combine function, if any */ + if (OidIsValid(combinefn)) + { + ObjectAddressSet(referenced, ProcedureRelationId, combinefn); + add_exact_object_address(&referenced, addrs); + } + + /* Depends on serialization function, if any */ + if (OidIsValid(serialfn)) + { + ObjectAddressSet(referenced, ProcedureRelationId, serialfn); + add_exact_object_address(&referenced, addrs); + } + + /* Depends on deserialization function, if any */ + if (OidIsValid(deserialfn)) + { + ObjectAddressSet(referenced, ProcedureRelationId, deserialfn); + add_exact_object_address(&referenced, addrs); + } + + /* Depends on forward transition function, if any */ + if (OidIsValid(mtransfn)) + { + ObjectAddressSet(referenced, ProcedureRelationId, mtransfn); + add_exact_object_address(&referenced, addrs); + } + + /* Depends on inverse transition function, if any */ + if (OidIsValid(minvtransfn)) + { + ObjectAddressSet(referenced, ProcedureRelationId, minvtransfn); + add_exact_object_address(&referenced, addrs); + } + + /* Depends on final function, if any */ + if (OidIsValid(mfinalfn)) + { + ObjectAddressSet(referenced, ProcedureRelationId, mfinalfn); + add_exact_object_address(&referenced, addrs); + } + + /* Depends on sort operator, if any */ + if (OidIsValid(sortop)) + { + ObjectAddressSet(referenced, OperatorRelationId, sortop); + add_exact_object_address(&referenced, addrs); + } + + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + free_object_addresses(addrs); + return myself; +} + +/* + * lookup_agg_function + * common code for finding aggregate support functions + * + * fnName: possibly-schema-qualified function name + * nargs, input_types: expected function argument types + * variadicArgType: type of variadic argument if any, else InvalidOid + * + * Returns OID of function, and stores its return type into *rettype + * + * NB: must not scribble on input_types[], as we may re-use those + */ +static Oid +lookup_agg_function(List *fnName, + int nargs, + Oid *input_types, + Oid variadicArgType, + Oid *rettype) +{ + Oid fnOid; + bool retset; + int nvargs; + Oid vatype; + Oid *true_oid_array; + FuncDetailCode fdresult; + AclResult aclresult; + int i; + + /* + * func_get_detail looks up the function in the catalogs, does + * disambiguation for polymorphic functions, handles inheritance, and + * returns the funcid and type and set or singleton status of the + * function's return value. it also returns the true argument types to + * the function. + */ + fdresult = func_get_detail(fnName, NIL, NIL, + nargs, input_types, false, false, false, + &fnOid, rettype, &retset, + &nvargs, &vatype, + &true_oid_array, NULL); + + /* only valid case is a normal function not returning a set */ + if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", + func_signature_string(fnName, nargs, + NIL, input_types)))); + if (retset) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function %s returns a set", + func_signature_string(fnName, nargs, + NIL, input_types)))); + + /* + * If the agg is declared to take VARIADIC ANY, the underlying functions + * had better be declared that way too, else they may receive too many + * parameters; but func_get_detail would have been happy with plain ANY. + * (Probably nothing very bad would happen, but it wouldn't work as the + * user expects.) Other combinations should work without any special + * pushups, given that we told func_get_detail not to expand VARIADIC. + */ + if (variadicArgType == ANYOID && vatype != ANYOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function %s must accept VARIADIC ANY to be used in this aggregate", + func_signature_string(fnName, nargs, + NIL, input_types)))); + + /* + * If there are any polymorphic types involved, enforce consistency, and + * possibly refine the result type. It's OK if the result is still + * polymorphic at this point, though. + */ + *rettype = enforce_generic_type_consistency(input_types, + true_oid_array, + nargs, + *rettype, + true); + + /* + * func_get_detail will find functions requiring run-time argument type + * coercion, but nodeAgg.c isn't prepared to deal with that + */ + for (i = 0; i < nargs; i++) + { + if (!IsBinaryCoercible(input_types[i], true_oid_array[i])) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function %s requires run-time type coercion", + func_signature_string(fnName, nargs, + NIL, true_oid_array)))); + } + + /* Check aggregate creator has permission to call the function */ + aclresult = pg_proc_aclcheck(fnOid, GetUserId(), ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(fnOid)); + + return fnOid; +} |