| /*------------------------------------------------------------------------- |
| * |
| * pg_aggregate.c |
| * routines to support manipulation of the pg_aggregate relation |
| * |
| * Portions Copyright (c) 1996-2023, 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 "cdb/cdbvars.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, |
| bool aggrepsafeexec, |
| 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 = object_aclcheck(TypeRelationId, aggArgTypes[i], GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error_type(aclresult, aggArgTypes[i]); |
| } |
| |
| aclresult = object_aclcheck(TypeRelationId, aggTransType, GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error_type(aclresult, aggTransType); |
| |
| if (OidIsValid(aggmTransType)) |
| { |
| aclresult = object_aclcheck(TypeRelationId, aggmTransType, GetUserId(), ACL_USAGE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error_type(aclresult, aggmTransType); |
| } |
| |
| aclresult = object_aclcheck(TypeRelationId, 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 */ |
| InvalidOid, /* no describe function */ |
| "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 */ |
| PRODATAACCESS_NONE, /* prodataaccess */ |
| PROEXECLOCATION_ANY); /* proexeclocation */ |
| 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); |
| values[Anum_pg_aggregate_aggrepsafeexec - 1] = BoolGetDatum(aggrepsafeexec); |
| 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 = object_aclcheck(ProcedureRelationId, fnOid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(fnOid)); |
| |
| return fnOid; |
| } |