/*------------------------------------------------------------------------- * * pg_proc.c * routines to support manipulation of the pg_proc 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_proc.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/htup_details.h" #include "access/table.h" #include "access/xact.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaccess.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_transform.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "executor/functions.h" #include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "parser/analyze.h" #include "parser/parse_coerce.h" #include "parser/parse_type.h" #include "pgstat.h" #include "rewrite/rewriteHandler.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/regproc.h" #include "utils/rel.h" #include "utils/syscache.h" typedef struct { char *proname; char *prosrc; } parse_error_callback_arg; static void sql_function_parse_error_callback(void *arg); static int match_prosrc_to_query(const char *prosrc, const char *queryText, int cursorpos); static bool match_prosrc_to_literal(const char *prosrc, const char *literal, int cursorpos, int *newcursorpos); /* ---------------------------------------------------------------- * ProcedureCreate * * Note: allParameterTypes, parameterModes, parameterNames, trftypes, and proconfig * are either arrays of the proper types or NULL. We declare them Datum, * not "ArrayType *", to avoid importing array.h into pg_proc.h. * ---------------------------------------------------------------- */ ObjectAddress ProcedureCreate(const char *procedureName, Oid procNamespace, bool replace, bool returnsSet, Oid returnType, Oid proowner, Oid languageObjectId, Oid languageValidator, const char *prosrc, const char *probin, Node *prosqlbody, char prokind, bool security_definer, bool isLeakProof, bool isStrict, char volatility, char parallel, oidvector *parameterTypes, Datum allParameterTypes, Datum parameterModes, Datum parameterNames, List *parameterDefaults, Datum trftypes, Datum proconfig, Oid prosupport, float4 procost, float4 prorows) { Oid retval; int parameterCount; int allParamCount; Oid *allParams; char *paramModes = NULL; Oid variadicType = InvalidOid; Acl *proacl = NULL; Relation rel; HeapTuple tup; HeapTuple oldtup; bool nulls[Natts_pg_proc]; Datum values[Natts_pg_proc]; bool replaces[Natts_pg_proc]; NameData procname; TupleDesc tupDesc; bool is_update; ObjectAddress myself, referenced; char *detailmsg; int i; Oid trfid; ObjectAddresses *addrs; /* * sanity checks */ Assert(PointerIsValid(prosrc)); parameterCount = parameterTypes->dim1; if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg_plural("functions cannot have more than %d argument", "functions cannot have more than %d arguments", FUNC_MAX_ARGS, FUNC_MAX_ARGS))); /* note: the above is correct, we do NOT count output arguments */ /* Deconstruct array inputs */ if (allParameterTypes != PointerGetDatum(NULL)) { /* * We expect the array to be a 1-D OID array; verify that. We don't * need to use deconstruct_array() since the array data is just going * to look like a C array of OID values. */ ArrayType *allParamArray = (ArrayType *) DatumGetPointer(allParameterTypes); allParamCount = ARR_DIMS(allParamArray)[0]; if (ARR_NDIM(allParamArray) != 1 || allParamCount <= 0 || ARR_HASNULL(allParamArray) || ARR_ELEMTYPE(allParamArray) != OIDOID) elog(ERROR, "allParameterTypes is not a 1-D Oid array"); allParams = (Oid *) ARR_DATA_PTR(allParamArray); Assert(allParamCount >= parameterCount); /* we assume caller got the contents right */ } else { allParamCount = parameterCount; allParams = parameterTypes->values; } if (parameterModes != PointerGetDatum(NULL)) { /* * We expect the array to be a 1-D CHAR array; verify that. We don't * need to use deconstruct_array() since the array data is just going * to look like a C array of char values. */ ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes); if (ARR_NDIM(modesArray) != 1 || ARR_DIMS(modesArray)[0] != allParamCount || ARR_HASNULL(modesArray) || ARR_ELEMTYPE(modesArray) != CHAROID) elog(ERROR, "parameterModes is not a 1-D char array"); paramModes = (char *) ARR_DATA_PTR(modesArray); } /* * Do not allow polymorphic return type unless there is a polymorphic * input argument that we can use to deduce the actual return type. */ detailmsg = check_valid_polymorphic_signature(returnType, parameterTypes->values, parameterCount); if (detailmsg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine result data type"), errdetail_internal("%s", detailmsg))); /* * Also, do not allow return type INTERNAL unless at least one input * argument is INTERNAL. */ detailmsg = check_valid_internal_signature(returnType, parameterTypes->values, parameterCount); if (detailmsg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("unsafe use of pseudo-type \"internal\""), errdetail_internal("%s", detailmsg))); /* * Apply the same tests to any OUT arguments. */ if (allParameterTypes != PointerGetDatum(NULL)) { for (i = 0; i < allParamCount; i++) { if (paramModes == NULL || paramModes[i] == PROARGMODE_IN || paramModes[i] == PROARGMODE_VARIADIC) continue; /* ignore input-only params */ detailmsg = check_valid_polymorphic_signature(allParams[i], parameterTypes->values, parameterCount); if (detailmsg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine result data type"), errdetail_internal("%s", detailmsg))); detailmsg = check_valid_internal_signature(allParams[i], parameterTypes->values, parameterCount); if (detailmsg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("unsafe use of pseudo-type \"internal\""), errdetail_internal("%s", detailmsg))); } } /* Identify variadic argument type, if any */ if (paramModes != NULL) { /* * Only the last input parameter can be variadic; if it is, save its * element type. Errors here are just elog since caller should have * checked this already. */ for (i = 0; i < allParamCount; i++) { switch (paramModes[i]) { case PROARGMODE_IN: case PROARGMODE_INOUT: if (OidIsValid(variadicType)) elog(ERROR, "variadic parameter must be last"); break; case PROARGMODE_OUT: if (OidIsValid(variadicType) && prokind == PROKIND_PROCEDURE) elog(ERROR, "variadic parameter must be last"); break; case PROARGMODE_TABLE: /* okay */ break; case PROARGMODE_VARIADIC: if (OidIsValid(variadicType)) elog(ERROR, "variadic parameter must be last"); switch (allParams[i]) { case ANYOID: variadicType = ANYOID; break; case ANYARRAYOID: variadicType = ANYELEMENTOID; break; case ANYCOMPATIBLEARRAYOID: variadicType = ANYCOMPATIBLEOID; break; default: variadicType = get_element_type(allParams[i]); if (!OidIsValid(variadicType)) elog(ERROR, "variadic parameter is not an array"); break; } break; default: elog(ERROR, "invalid parameter mode '%c'", paramModes[i]); break; } } } /* * All seems OK; prepare the data to be inserted into pg_proc. */ for (i = 0; i < Natts_pg_proc; ++i) { nulls[i] = false; values[i] = (Datum) 0; replaces[i] = true; } namestrcpy(&procname, procedureName); values[Anum_pg_proc_proname - 1] = NameGetDatum(&procname); values[Anum_pg_proc_pronamespace - 1] = ObjectIdGetDatum(procNamespace); values[Anum_pg_proc_proowner - 1] = ObjectIdGetDatum(proowner); values[Anum_pg_proc_prolang - 1] = ObjectIdGetDatum(languageObjectId); values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost); values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows); values[Anum_pg_proc_provariadic - 1] = ObjectIdGetDatum(variadicType); values[Anum_pg_proc_prosupport - 1] = ObjectIdGetDatum(prosupport); values[Anum_pg_proc_prokind - 1] = CharGetDatum(prokind); values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer); values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof); values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict); values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet); values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility); values[Anum_pg_proc_proparallel - 1] = CharGetDatum(parallel); values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount); values[Anum_pg_proc_pronargdefaults - 1] = UInt16GetDatum(list_length(parameterDefaults)); values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType); values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes); if (allParameterTypes != PointerGetDatum(NULL)) values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes; else nulls[Anum_pg_proc_proallargtypes - 1] = true; if (parameterModes != PointerGetDatum(NULL)) values[Anum_pg_proc_proargmodes - 1] = parameterModes; else nulls[Anum_pg_proc_proargmodes - 1] = true; if (parameterNames != PointerGetDatum(NULL)) values[Anum_pg_proc_proargnames - 1] = parameterNames; else nulls[Anum_pg_proc_proargnames - 1] = true; if (parameterDefaults != NIL) values[Anum_pg_proc_proargdefaults - 1] = CStringGetTextDatum(nodeToString(parameterDefaults)); else nulls[Anum_pg_proc_proargdefaults - 1] = true; if (trftypes != PointerGetDatum(NULL)) values[Anum_pg_proc_protrftypes - 1] = trftypes; else nulls[Anum_pg_proc_protrftypes - 1] = true; values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc); if (probin) values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin); else nulls[Anum_pg_proc_probin - 1] = true; if (prosqlbody) values[Anum_pg_proc_prosqlbody - 1] = CStringGetTextDatum(nodeToString(prosqlbody)); else nulls[Anum_pg_proc_prosqlbody - 1] = true; if (proconfig != PointerGetDatum(NULL)) values[Anum_pg_proc_proconfig - 1] = proconfig; else nulls[Anum_pg_proc_proconfig - 1] = true; /* proacl will be determined later */ rel = table_open(ProcedureRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); /* Check for pre-existing definition */ oldtup = SearchSysCache3(PROCNAMEARGSNSP, PointerGetDatum(procedureName), PointerGetDatum(parameterTypes), ObjectIdGetDatum(procNamespace)); if (HeapTupleIsValid(oldtup)) { /* There is one; okay to replace it? */ Form_pg_proc oldproc = (Form_pg_proc) GETSTRUCT(oldtup); Datum proargnames; bool isnull; const char *dropcmd; if (!replace) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_FUNCTION), errmsg("function \"%s\" already exists with same argument types", procedureName))); if (!object_ownercheck(ProcedureRelationId, oldproc->oid, proowner)) aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, procedureName); /* Not okay to change routine kind */ if (oldproc->prokind != prokind) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot change routine kind"), (oldproc->prokind == PROKIND_AGGREGATE ? errdetail("\"%s\" is an aggregate function.", procedureName) : oldproc->prokind == PROKIND_FUNCTION ? errdetail("\"%s\" is a function.", procedureName) : oldproc->prokind == PROKIND_PROCEDURE ? errdetail("\"%s\" is a procedure.", procedureName) : oldproc->prokind == PROKIND_WINDOW ? errdetail("\"%s\" is a window function.", procedureName) : 0))); dropcmd = (prokind == PROKIND_PROCEDURE ? "DROP PROCEDURE" : prokind == PROKIND_AGGREGATE ? "DROP AGGREGATE" : "DROP FUNCTION"); /* * Not okay to change the return type of the existing proc, since * existing rules, views, etc may depend on the return type. * * In case of a procedure, a changing return type means that whether * the procedure has output parameters was changed. Since there is no * user visible return type, we produce a more specific error message. */ if (returnType != oldproc->prorettype || returnsSet != oldproc->proretset) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), prokind == PROKIND_PROCEDURE ? errmsg("cannot change whether a procedure has output parameters") : errmsg("cannot change return type of existing function"), /* * translator: first %s is DROP FUNCTION, DROP PROCEDURE, or DROP * AGGREGATE */ errhint("Use %s %s first.", dropcmd, format_procedure(oldproc->oid)))); /* * If it returns RECORD, check for possible change of record type * implied by OUT parameters */ if (returnType == RECORDOID) { TupleDesc olddesc; TupleDesc newdesc; olddesc = build_function_result_tupdesc_t(oldtup); newdesc = build_function_result_tupdesc_d(prokind, allParameterTypes, parameterModes, parameterNames); if (olddesc == NULL && newdesc == NULL) /* ok, both are runtime-defined RECORDs */ ; else if (olddesc == NULL || newdesc == NULL || !equalTupleDescs(olddesc, newdesc)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot change return type of existing function"), errdetail("Row type defined by OUT parameters is different."), /* translator: first %s is DROP FUNCTION or DROP PROCEDURE */ errhint("Use %s %s first.", dropcmd, format_procedure(oldproc->oid)))); } /* * If there were any named input parameters, check to make sure the * names have not been changed, as this could break existing calls. We * allow adding names to formerly unnamed parameters, though. */ proargnames = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup, Anum_pg_proc_proargnames, &isnull); if (!isnull) { Datum proargmodes; char **old_arg_names; char **new_arg_names; int n_old_arg_names; int n_new_arg_names; int j; proargmodes = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup, Anum_pg_proc_proargmodes, &isnull); if (isnull) proargmodes = PointerGetDatum(NULL); /* just to be sure */ n_old_arg_names = get_func_input_arg_names(proargnames, proargmodes, &old_arg_names); n_new_arg_names = get_func_input_arg_names(parameterNames, parameterModes, &new_arg_names); for (j = 0; j < n_old_arg_names; j++) { if (old_arg_names[j] == NULL) continue; if (j >= n_new_arg_names || new_arg_names[j] == NULL || strcmp(old_arg_names[j], new_arg_names[j]) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot change name of input parameter \"%s\"", old_arg_names[j]), /* translator: first %s is DROP FUNCTION or DROP PROCEDURE */ errhint("Use %s %s first.", dropcmd, format_procedure(oldproc->oid)))); } } /* * If there are existing defaults, check compatibility: redefinition * must not remove any defaults nor change their types. (Removing a * default might cause a function to fail to satisfy an existing call. * Changing type would only be possible if the associated parameter is * polymorphic, and in such cases a change of default type might alter * the resolved output type of existing calls.) */ if (oldproc->pronargdefaults != 0) { Datum proargdefaults; List *oldDefaults; ListCell *oldlc; ListCell *newlc; if (list_length(parameterDefaults) < oldproc->pronargdefaults) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot remove parameter defaults from existing function"), /* translator: first %s is DROP FUNCTION or DROP PROCEDURE */ errhint("Use %s %s first.", dropcmd, format_procedure(oldproc->oid)))); proargdefaults = SysCacheGetAttrNotNull(PROCNAMEARGSNSP, oldtup, Anum_pg_proc_proargdefaults); oldDefaults = castNode(List, stringToNode(TextDatumGetCString(proargdefaults))); Assert(list_length(oldDefaults) == oldproc->pronargdefaults); /* new list can have more defaults than old, advance over 'em */ newlc = list_nth_cell(parameterDefaults, list_length(parameterDefaults) - oldproc->pronargdefaults); foreach(oldlc, oldDefaults) { Node *oldDef = (Node *) lfirst(oldlc); Node *newDef = (Node *) lfirst(newlc); if (exprType(oldDef) != exprType(newDef)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot change data type of existing parameter default value"), /* translator: first %s is DROP FUNCTION or DROP PROCEDURE */ errhint("Use %s %s first.", dropcmd, format_procedure(oldproc->oid)))); newlc = lnext(parameterDefaults, newlc); } } /* * Do not change existing oid, ownership or permissions, either. Note * dependency-update code below has to agree with this decision. */ replaces[Anum_pg_proc_oid - 1] = false; replaces[Anum_pg_proc_proowner - 1] = false; replaces[Anum_pg_proc_proacl - 1] = false; /* Okay, do it... */ tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces); CatalogTupleUpdate(rel, &tup->t_self, tup); ReleaseSysCache(oldtup); is_update = true; } else { /* Creating a new procedure */ Oid newOid; /* First, get default permissions and set up proacl */ proacl = get_user_default_acl(OBJECT_FUNCTION, proowner, procNamespace); if (proacl != NULL) values[Anum_pg_proc_proacl - 1] = PointerGetDatum(proacl); else nulls[Anum_pg_proc_proacl - 1] = true; newOid = GetNewOidWithIndex(rel, ProcedureOidIndexId, Anum_pg_proc_oid); values[Anum_pg_proc_oid - 1] = ObjectIdGetDatum(newOid); tup = heap_form_tuple(tupDesc, values, nulls); CatalogTupleInsert(rel, tup); is_update = false; } retval = ((Form_pg_proc) GETSTRUCT(tup))->oid; /* * Create dependencies for the new function. If we are updating an * existing function, first delete any existing pg_depend entries. * (However, since we are not changing ownership or permissions, the * shared dependencies do *not* need to change, and we leave them alone.) */ if (is_update) deleteDependencyRecordsFor(ProcedureRelationId, retval, true); addrs = new_object_addresses(); ObjectAddressSet(myself, ProcedureRelationId, retval); /* dependency on namespace */ ObjectAddressSet(referenced, NamespaceRelationId, procNamespace); add_exact_object_address(&referenced, addrs); /* dependency on implementation language */ ObjectAddressSet(referenced, LanguageRelationId, languageObjectId); add_exact_object_address(&referenced, addrs); /* dependency on return type */ ObjectAddressSet(referenced, TypeRelationId, returnType); add_exact_object_address(&referenced, addrs); /* dependency on transform used by return type, if any */ if ((trfid = get_transform_oid(returnType, languageObjectId, true))) { ObjectAddressSet(referenced, TransformRelationId, trfid); add_exact_object_address(&referenced, addrs); } /* dependency on parameter types */ for (i = 0; i < allParamCount; i++) { ObjectAddressSet(referenced, TypeRelationId, allParams[i]); add_exact_object_address(&referenced, addrs); /* dependency on transform used by parameter type, if any */ if ((trfid = get_transform_oid(allParams[i], languageObjectId, true))) { ObjectAddressSet(referenced, TransformRelationId, trfid); add_exact_object_address(&referenced, addrs); } } /* dependency on support function, if any */ if (OidIsValid(prosupport)) { ObjectAddressSet(referenced, ProcedureRelationId, prosupport); add_exact_object_address(&referenced, addrs); } record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); free_object_addresses(addrs); /* dependency on SQL routine body */ if (languageObjectId == SQLlanguageId && prosqlbody) recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL); /* dependency on parameter default expressions */ if (parameterDefaults) recordDependencyOnExpr(&myself, (Node *) parameterDefaults, NIL, DEPENDENCY_NORMAL); /* dependency on owner */ if (!is_update) recordDependencyOnOwner(ProcedureRelationId, retval, proowner); /* dependency on any roles mentioned in ACL */ if (!is_update) recordDependencyOnNewAcl(ProcedureRelationId, retval, 0, proowner, proacl); /* dependency on extension */ recordDependencyOnCurrentExtension(&myself, is_update); heap_freetuple(tup); /* Post creation hook for new function */ InvokeObjectPostCreateHook(ProcedureRelationId, retval, 0); table_close(rel, RowExclusiveLock); /* Verify function body */ if (OidIsValid(languageValidator)) { ArrayType *set_items = NULL; int save_nestlevel = 0; /* Advance command counter so new tuple can be seen by validator */ CommandCounterIncrement(); /* * Set per-function configuration parameters so that the validation is * done with the environment the function expects. However, if * check_function_bodies is off, we don't do this, because that would * create dump ordering hazards that pg_dump doesn't know how to deal * with. (For example, a SET clause might refer to a not-yet-created * text search configuration.) This means that the validator * shouldn't complain about anything that might depend on a GUC * parameter when check_function_bodies is off. */ if (check_function_bodies) { set_items = (ArrayType *) DatumGetPointer(proconfig); if (set_items) /* Need a new GUC nesting level */ { save_nestlevel = NewGUCNestLevel(); ProcessGUCArray(set_items, (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, GUC_ACTION_SAVE); } } OidFunctionCall1(languageValidator, ObjectIdGetDatum(retval)); if (set_items) AtEOXact_GUC(true, save_nestlevel); } /* ensure that stats are dropped if transaction aborts */ if (!is_update) pgstat_create_function(retval); return myself; } /* * Validator for internal functions * * Check that the given internal function name (the "prosrc" value) is * a known builtin function. */ Datum fmgr_internal_validator(PG_FUNCTION_ARGS) { Oid funcoid = PG_GETARG_OID(0); HeapTuple tuple; Datum tmp; char *prosrc; if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) PG_RETURN_VOID(); /* * We do not honor check_function_bodies since it's unlikely the function * name will be found later if it isn't there now. */ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcoid); tmp = SysCacheGetAttrNotNull(PROCOID, tuple, Anum_pg_proc_prosrc); prosrc = TextDatumGetCString(tmp); if (fmgr_internal_function(prosrc) == InvalidOid) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("there is no built-in function named \"%s\"", prosrc))); ReleaseSysCache(tuple); PG_RETURN_VOID(); } /* * Validator for C language functions * * Make sure that the library file exists, is loadable, and contains * the specified link symbol. Also check for a valid function * information record. */ Datum fmgr_c_validator(PG_FUNCTION_ARGS) { Oid funcoid = PG_GETARG_OID(0); void *libraryhandle; HeapTuple tuple; Datum tmp; char *prosrc; char *probin; if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) PG_RETURN_VOID(); /* * It'd be most consistent to skip the check if !check_function_bodies, * but the purpose of that switch is to be helpful for pg_dump loading, * and for pg_dump loading it's much better if we *do* check. */ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcoid); tmp = SysCacheGetAttrNotNull(PROCOID, tuple, Anum_pg_proc_prosrc); prosrc = TextDatumGetCString(tmp); tmp = SysCacheGetAttrNotNull(PROCOID, tuple, Anum_pg_proc_probin); probin = TextDatumGetCString(tmp); (void) load_external_function(probin, prosrc, true, &libraryhandle); (void) fetch_finfo_record(libraryhandle, prosrc); ReleaseSysCache(tuple); PG_RETURN_VOID(); } /* * Validator for SQL language functions * * Parse it here in order to be sure that it contains no syntax errors. */ Datum fmgr_sql_validator(PG_FUNCTION_ARGS) { Oid funcoid = PG_GETARG_OID(0); HeapTuple tuple; Form_pg_proc proc; List *raw_parsetree_list; List *querytree_list; ListCell *lc; bool isnull; Datum tmp; char *prosrc; parse_error_callback_arg callback_arg; ErrorContextCallback sqlerrcontext; bool haspolyarg; int i; if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) PG_RETURN_VOID(); tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); /* Disallow pseudotype result */ /* except for RECORD, VOID, or polymorphic */ if (get_typtype(proc->prorettype) == TYPTYPE_PSEUDO && proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && !IsPolymorphicType(proc->prorettype)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("SQL functions cannot return type %s", format_type_be(proc->prorettype)))); /* Disallow pseudotypes in arguments */ /* except for polymorphic */ haspolyarg = false; for (i = 0; i < proc->pronargs; i++) { if (get_typtype(proc->proargtypes.values[i]) == TYPTYPE_PSEUDO) { if (IsPolymorphicType(proc->proargtypes.values[i])) haspolyarg = true; else ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("SQL functions cannot have arguments of type %s", format_type_be(proc->proargtypes.values[i])))); } } /* Postpone body checks if !check_function_bodies */ if (check_function_bodies) { tmp = SysCacheGetAttrNotNull(PROCOID, tuple, Anum_pg_proc_prosrc); prosrc = TextDatumGetCString(tmp); /* * Setup error traceback support for ereport(). */ callback_arg.proname = NameStr(proc->proname); callback_arg.prosrc = prosrc; sqlerrcontext.callback = sql_function_parse_error_callback; sqlerrcontext.arg = (void *) &callback_arg; sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; /* If we have prosqlbody, pay attention to that not prosrc */ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull); if (!isnull) { Node *n; List *stored_query_list; n = stringToNode(TextDatumGetCString(tmp)); if (IsA(n, List)) stored_query_list = linitial(castNode(List, n)); else stored_query_list = list_make1(n); querytree_list = NIL; foreach(lc, stored_query_list) { Query *parsetree = lfirst_node(Query, lc); List *querytree_sublist; /* * Typically, we'd have acquired locks already while parsing * the body of the CREATE FUNCTION command. However, a * validator function cannot assume that it's only called in * that context. */ AcquireRewriteLocks(parsetree, true, false); querytree_sublist = pg_rewrite_query(parsetree); querytree_list = lappend(querytree_list, querytree_sublist); } } else { /* * We can't do full prechecking of 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. * * We can run the text through the raw parser though; this will at * least catch silly syntactic errors. */ raw_parsetree_list = pg_parse_query(prosrc); querytree_list = NIL; if (!haspolyarg) { /* * OK to do full precheck: analyze and rewrite the queries, * then verify the result type. */ SQLFunctionParseInfoPtr pinfo; /* But first, set up parameter information */ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid); foreach(lc, raw_parsetree_list) { RawStmt *parsetree = lfirst_node(RawStmt, lc); List *querytree_sublist; querytree_sublist = pg_analyze_and_rewrite_withcb(parsetree, prosrc, (ParserSetupHook) sql_fn_parser_setup, pinfo, NULL); querytree_list = lappend(querytree_list, querytree_sublist); } } } if (!haspolyarg) { Oid rettype; TupleDesc rettupdesc; check_sql_fn_statements(querytree_list); (void) get_func_result_type(funcoid, &rettype, &rettupdesc); (void) check_sql_fn_retval_ext(querytree_list, rettype, rettupdesc, proc->prokind, false, NULL); } error_context_stack = sqlerrcontext.previous; } ReleaseSysCache(tuple); PG_RETURN_VOID(); } /* * Error context callback for handling errors in SQL function definitions */ static void sql_function_parse_error_callback(void *arg) { parse_error_callback_arg *callback_arg = (parse_error_callback_arg *) arg; /* See if it's a syntax error; if so, transpose to CREATE FUNCTION */ if (!function_parse_error_transpose(callback_arg->prosrc)) { /* If it's not a syntax error, push info onto context stack */ errcontext("SQL function \"%s\"", callback_arg->proname); } } /* * Adjust a syntax error occurring inside the function body of a CREATE * FUNCTION or DO command. This can be used by any function validator or * anonymous-block handler, not only for SQL-language functions. * It is assumed that the syntax error position is initially relative to the * function body string (as passed in). If possible, we adjust the position * to reference the original command text; if we can't manage that, we set * up an "internal query" syntax error instead. * * Returns true if a syntax error was processed, false if not. */ bool function_parse_error_transpose(const char *prosrc) { int origerrposition; int newerrposition; /* * Nothing to do unless we are dealing with a syntax error that has a * cursor position. * * Some PLs may prefer to report the error position as an internal error * to begin with, so check that too. */ origerrposition = geterrposition(); if (origerrposition <= 0) { origerrposition = getinternalerrposition(); if (origerrposition <= 0) return false; } /* We can get the original query text from the active portal (hack...) */ if (ActivePortal && ActivePortal->status == PORTAL_ACTIVE) { const char *queryText = ActivePortal->sourceText; /* Try to locate the prosrc in the original text */ newerrposition = match_prosrc_to_query(prosrc, queryText, origerrposition); } else { /* * Quietly give up if no ActivePortal. This is an unusual situation * but it can happen in, e.g., logical replication workers. */ newerrposition = -1; } if (newerrposition > 0) { /* Successful, so fix error position to reference original query */ errposition(newerrposition); /* Get rid of any report of the error as an "internal query" */ internalerrposition(0); internalerrquery(NULL); } else { /* * If unsuccessful, convert the position to an internal position * marker and give the function text as the internal query. */ errposition(0); internalerrposition(origerrposition); internalerrquery(prosrc); } return true; } /* * Try to locate the string literal containing the function body in the * given text of the CREATE FUNCTION or DO command. If successful, return * the character (not byte) index within the command corresponding to the * given character index within the literal. If not successful, return 0. */ static int match_prosrc_to_query(const char *prosrc, const char *queryText, int cursorpos) { /* * Rather than fully parsing the original command, we just scan the * command looking for $prosrc$ or 'prosrc'. This could be fooled (though * not in any very probable scenarios), so fail if we find more than one * match. */ int prosrclen = strlen(prosrc); int querylen = strlen(queryText); int matchpos = 0; int curpos; int newcursorpos; for (curpos = 0; curpos < querylen - prosrclen; curpos++) { if (queryText[curpos] == '$' && strncmp(prosrc, &queryText[curpos + 1], prosrclen) == 0 && queryText[curpos + 1 + prosrclen] == '$') { /* * Found a $foo$ match. Since there are no embedded quoting * characters in a dollar-quoted literal, we don't have to do any * fancy arithmetic; just offset by the starting position. */ if (matchpos) return 0; /* multiple matches, fail */ matchpos = pg_mbstrlen_with_len(queryText, curpos + 1) + cursorpos; } else if (queryText[curpos] == '\'' && match_prosrc_to_literal(prosrc, &queryText[curpos + 1], cursorpos, &newcursorpos)) { /* * Found a 'foo' match. match_prosrc_to_literal() has adjusted * for any quotes or backslashes embedded in the literal. */ if (matchpos) return 0; /* multiple matches, fail */ matchpos = pg_mbstrlen_with_len(queryText, curpos + 1) + newcursorpos; } } return matchpos; } /* * Try to match the given source text to a single-quoted literal. * If successful, adjust newcursorpos to correspond to the character * (not byte) index corresponding to cursorpos in the source text. * * At entry, literal points just past a ' character. We must check for the * trailing quote. */ static bool match_prosrc_to_literal(const char *prosrc, const char *literal, int cursorpos, int *newcursorpos) { int newcp = cursorpos; int chlen; /* * This implementation handles backslashes and doubled quotes in the * string literal. It does not handle the SQL syntax for literals * continued across line boundaries. * * We do the comparison a character at a time, not a byte at a time, so * that we can do the correct cursorpos math. */ while (*prosrc) { cursorpos--; /* characters left before cursor */ /* * Check for backslashes and doubled quotes in the literal; adjust * newcp when one is found before the cursor. */ if (*literal == '\\') { literal++; if (cursorpos > 0) newcp++; } else if (*literal == '\'') { if (literal[1] != '\'') goto fail; literal++; if (cursorpos > 0) newcp++; } chlen = pg_mblen(prosrc); if (strncmp(prosrc, literal, chlen) != 0) goto fail; prosrc += chlen; literal += chlen; } if (*literal == '\'' && literal[1] != '\'') { /* success */ *newcursorpos = newcp; return true; } fail: /* Must set *newcursorpos to suppress compiler warning */ *newcursorpos = newcp; return false; } List * oid_array_to_list(Datum datum) { ArrayType *array = DatumGetArrayTypeP(datum); Datum *values; int nelems; int i; List *result = NIL; deconstruct_array_builtin(array, OIDOID, &values, NULL, &nelems); for (i = 0; i < nelems; i++) result = lappend_oid(result, values[i]); return result; }