X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fcatalog%2Fpg_proc.c;h=abf2f497e411818c9ce58ec4286a8534c86b862a;hb=537cbd35c893e67a63c59bc636c3e888bd228bc7;hp=1b658c9ad2693b4d5e24733d2b99dbb2cd48e513;hpb=b6b71b85bc45b49005b5aec87cba2c33fc8baf49;p=postgresql diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 1b658c9ad2..abf2f497e4 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -3,48 +3,52 @@ * pg_proc.c * routines to support manipulation of the pg_proc relation * - * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.119 2004/08/29 05:06:41 momjian Exp $ + * src/backend/catalog/pg_proc.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "access/heapam.h" -#include "catalog/catname.h" +#include "access/htup_details.h" +#include "access/xact.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 "executor/executor.h" -#include "fmgr.h" -#include "miscadmin.h" +#include "catalog/pg_proc_fn.h" +#include "catalog/pg_type.h" +#include "executor/functions.h" +#include "funcapi.h" #include "mb/pg_wchar.h" -#include "parser/parse_coerce.h" -#include "parser/parse_expr.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" #include "parser/parse_type.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/syscache.h" -/* GUC parameter */ -bool check_function_bodies = true; - - Datum fmgr_internal_validator(PG_FUNCTION_ARGS); Datum fmgr_c_validator(PG_FUNCTION_ARGS); Datum fmgr_sql_validator(PG_FUNCTION_ARGS); -static Datum create_parameternames_array(int parameterCount, - const char *parameterNames[]); +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); @@ -54,6 +58,10 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal, /* ---------------------------------------------------------------- * ProcedureCreate + * + * Note: allParameterTypes, parameterModes, parameterNames, 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_fn.h. * ---------------------------------------------------------------- */ Oid @@ -62,91 +70,248 @@ ProcedureCreate(const char *procedureName, bool replace, bool returnsSet, Oid returnType, + Oid proowner, Oid languageObjectId, Oid languageValidator, const char *prosrc, const char *probin, bool isAgg, + bool isWindowFunc, bool security_definer, + bool isLeakProof, bool isStrict, char volatility, - int parameterCount, - const Oid *parameterTypes, - const char *parameterNames[]) + oidvector *parameterTypes, + Datum allParameterTypes, + Datum parameterModes, + Datum parameterNames, + List *parameterDefaults, + Datum proconfig, + float4 procost, + float4 prorows) { - int i; + Oid retval; + int parameterCount; + int allParamCount; + Oid *allParams; + char *paramModes = NULL; + bool genericInParam = false; + bool genericOutParam = false; + bool anyrangeInParam = false; + bool anyrangeOutParam = false; + bool internalInParam = false; + bool internalOutParam = false; + Oid variadicType = InvalidOid; + Acl *proacl = NULL; Relation rel; HeapTuple tup; HeapTuple oldtup; - char nulls[Natts_pg_proc]; + bool nulls[Natts_pg_proc]; Datum values[Natts_pg_proc]; - char replaces[Natts_pg_proc]; - Oid typev[FUNC_MAX_ARGS]; - Datum namesarray; + bool replaces[Natts_pg_proc]; Oid relid; NameData procname; TupleDesc tupDesc; - Oid retval; bool is_update; ObjectAddress myself, referenced; + int i; /* * sanity checks */ Assert(PointerIsValid(prosrc)); - Assert(PointerIsValid(probin)); + parameterCount = parameterTypes->dim1; if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg("functions cannot have more than %d arguments", - FUNC_MAX_ARGS))); + 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 return type ANYARRAY or ANYELEMENT unless at least one - * argument is also ANYARRAY or ANYELEMENT + * Detect whether we have polymorphic or INTERNAL arguments. The first + * loop checks input arguments, the second output arguments. */ - if (returnType == ANYARRAYOID || returnType == ANYELEMENTOID) + for (i = 0; i < parameterCount; i++) { - bool genericParam = false; + switch (parameterTypes->values[i]) + { + case ANYARRAYOID: + case ANYELEMENTOID: + case ANYNONARRAYOID: + case ANYENUMOID: + genericInParam = true; + break; + case ANYRANGEOID: + genericInParam = true; + anyrangeInParam = true; + break; + case INTERNALOID: + internalInParam = true; + break; + } + } - for (i = 0; i < parameterCount; i++) + if (allParameterTypes != PointerGetDatum(NULL)) + { + for (i = 0; i < allParamCount; i++) { - if (parameterTypes[i] == ANYARRAYOID || - parameterTypes[i] == ANYELEMENTOID) + if (paramModes == NULL || + paramModes[i] == PROARGMODE_IN || + paramModes[i] == PROARGMODE_VARIADIC) + continue; /* ignore input-only params */ + + switch (allParams[i]) { - genericParam = true; - break; + case ANYARRAYOID: + case ANYELEMENTOID: + case ANYNONARRAYOID: + case ANYENUMOID: + genericOutParam = true; + break; + case ANYRANGEOID: + genericOutParam = true; + anyrangeOutParam = true; + break; + case INTERNALOID: + internalOutParam = true; + break; } } - - if (!genericParam) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("cannot determine result data type"), - errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type."))); } - /* Make sure we have a zero-padded param type array */ - MemSet(typev, 0, FUNC_MAX_ARGS * sizeof(Oid)); - if (parameterCount > 0) - memcpy(typev, parameterTypes, parameterCount * sizeof(Oid)); + /* + * Do not allow polymorphic return type unless at least one input argument + * is polymorphic. ANYRANGE return type is even stricter: must have an + * ANYRANGE input (since we can't deduce the specific range type from + * ANYELEMENT). Also, do not allow return type INTERNAL unless at least + * one input argument is INTERNAL. + */ + if ((IsPolymorphicType(returnType) || genericOutParam) + && !genericInParam) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine result data type"), + errdetail("A function returning a polymorphic type must have at least one polymorphic argument."))); + + if ((returnType == ANYRANGEOID || anyrangeOutParam) && + !anyrangeInParam) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine result data type"), + errdetail("A function returning \"anyrange\" must have at least one \"anyrange\" argument."))); - /* Process param names, if given */ - namesarray = create_parameternames_array(parameterCount, parameterNames); + if ((returnType == INTERNALOID || internalOutParam) && !internalInParam) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("unsafe use of pseudo-type \"internal\""), + errdetail("A function returning \"internal\" must have at least one \"internal\" argument."))); /* * don't allow functions of complex types that have the same name as * existing attributes of the type */ - if (parameterCount == 1 && OidIsValid(typev[0]) && - (relid = typeidTypeRelid(typev[0])) != InvalidOid && - get_attnum(relid, (char *) procedureName) != InvalidAttrNumber) + if (parameterCount == 1 && + OidIsValid(parameterTypes->values[0]) && + (relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid && + get_attnum(relid, procedureName) != InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN), errmsg("\"%s\" is already an attribute of type %s", - procedureName, format_type_be(typev[0])))); + procedureName, + format_type_be(parameterTypes->values[0])))); + + 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: + 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; + 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. @@ -154,55 +319,80 @@ ProcedureCreate(const char *procedureName, for (i = 0; i < Natts_pg_proc; ++i) { - nulls[i] = ' '; - values[i] = (Datum) NULL; - replaces[i] = 'r'; + nulls[i] = false; + values[i] = (Datum) 0; + replaces[i] = true; } - i = 0; namestrcpy(&procname, procedureName); - values[i++] = NameGetDatum(&procname); /* proname */ - values[i++] = ObjectIdGetDatum(procNamespace); /* pronamespace */ - values[i++] = Int32GetDatum(GetUserId()); /* proowner */ - values[i++] = ObjectIdGetDatum(languageObjectId); /* prolang */ - values[i++] = BoolGetDatum(isAgg); /* proisagg */ - values[i++] = BoolGetDatum(security_definer); /* prosecdef */ - values[i++] = BoolGetDatum(isStrict); /* proisstrict */ - values[i++] = BoolGetDatum(returnsSet); /* proretset */ - values[i++] = CharGetDatum(volatility); /* provolatile */ - values[i++] = UInt16GetDatum(parameterCount); /* pronargs */ - values[i++] = ObjectIdGetDatum(returnType); /* prorettype */ - values[i++] = PointerGetDatum(typev); /* proargtypes */ - values[i++] = namesarray; /* proargnames */ - if (namesarray == PointerGetDatum(NULL)) - nulls[Anum_pg_proc_proargnames - 1] = 'n'; - values[i++] = DirectFunctionCall1(textin, /* prosrc */ - CStringGetDatum(prosrc)); - values[i++] = DirectFunctionCall1(textin, /* probin */ - CStringGetDatum(probin)); - /* proacl will be handled below */ - - rel = heap_openr(ProcedureRelationName, RowExclusiveLock); - tupDesc = rel->rd_att; + 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_protransform - 1] = ObjectIdGetDatum(InvalidOid); + values[Anum_pg_proc_proisagg - 1] = BoolGetDatum(isAgg); + values[Anum_pg_proc_proiswindow - 1] = BoolGetDatum(isWindowFunc); + 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_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; + 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 (proconfig != PointerGetDatum(NULL)) + values[Anum_pg_proc_proconfig - 1] = proconfig; + else + nulls[Anum_pg_proc_proconfig - 1] = true; + /* proacl will be determined later */ + + rel = heap_open(ProcedureRelationId, RowExclusiveLock); + tupDesc = RelationGetDescr(rel); /* Check for pre-existing definition */ - oldtup = SearchSysCache(PROCNAMENSP, - PointerGetDatum(procedureName), - UInt16GetDatum(parameterCount), - PointerGetDatum(typev), - ObjectIdGetDatum(procNamespace)); + 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; if (!replace) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_FUNCTION), - errmsg("function \"%s\" already exists with same argument types", - procedureName))); - if (GetUserId() != oldproc->proowner && !superuser()) + errmsg("function \"%s\" already exists with same argument types", + procedureName))); + if (!pg_proc_ownercheck(HeapTupleGetOid(oldtup), proowner)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, procedureName); @@ -214,30 +404,168 @@ ProcedureCreate(const char *procedureName, returnsSet != oldproc->proretset) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("cannot change return type of existing function"), - errhint("Use DROP FUNCTION first."))); + errmsg("cannot change return type of existing function"), + errhint("Use DROP FUNCTION %s first.", + format_procedure(HeapTupleGetOid(oldtup))))); + + /* + * 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(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."), + errhint("Use DROP FUNCTION %s first.", + format_procedure(HeapTupleGetOid(oldtup))))); + } - /* Can't change aggregate status, either */ + /* + * 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]), + errhint("Use DROP FUNCTION %s first.", + format_procedure(HeapTupleGetOid(oldtup))))); + } + } + + /* + * 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"), + errhint("Use DROP FUNCTION %s first.", + format_procedure(HeapTupleGetOid(oldtup))))); + + proargdefaults = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup, + Anum_pg_proc_proargdefaults, + &isnull); + Assert(!isnull); + oldDefaults = (List *) stringToNode(TextDatumGetCString(proargdefaults)); + Assert(IsA(oldDefaults, List)); + Assert(list_length(oldDefaults) == oldproc->pronargdefaults); + + /* new list can have more defaults than old, advance over 'em */ + newlc = list_head(parameterDefaults); + for (i = list_length(parameterDefaults) - oldproc->pronargdefaults; + i > 0; + i--) + newlc = lnext(newlc); + + 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"), + errhint("Use DROP FUNCTION %s first.", + format_procedure(HeapTupleGetOid(oldtup))))); + newlc = lnext(newlc); + } + } + + /* Can't change aggregate or window-function status, either */ if (oldproc->proisagg != isAgg) { if (oldproc->proisagg) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("function \"%s\" is an aggregate", + errmsg("function \"%s\" is an aggregate function", + procedureName))); + else + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("function \"%s\" is not an aggregate function", + procedureName))); + } + if (oldproc->proiswindow != isWindowFunc) + { + if (oldproc->proiswindow) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("function \"%s\" is a window function", procedureName))); else ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("function \"%s\" is not an aggregate", + errmsg("function \"%s\" is not a window function", procedureName))); } - /* do not change existing ownership or permissions, either */ - replaces[Anum_pg_proc_proowner - 1] = ' '; - replaces[Anum_pg_proc_proacl - 1] = ' '; + /* + * Do not change existing ownership or permissions, either. Note + * dependency-update code below has to agree with this decision. + */ + replaces[Anum_pg_proc_proowner - 1] = false; + replaces[Anum_pg_proc_proacl - 1] = false; /* Okay, do it... */ - tup = heap_modifytuple(oldtup, rel, values, nulls, replaces); + tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces); simple_heap_update(rel, &tup->t_self, tup); ReleaseSysCache(oldtup); @@ -247,10 +575,15 @@ ProcedureCreate(const char *procedureName, { /* Creating a new procedure */ - /* start out with empty permissions */ - nulls[Anum_pg_proc_proacl - 1] = 'n'; + /* First, get default permissions and set up proacl */ + proacl = get_user_default_acl(ACL_OBJECT_FUNCTION, proowner, + procNamespace); + if (proacl != NULL) + values[Anum_pg_proc_proacl - 1] = PointerGetDatum(proacl); + else + nulls[Anum_pg_proc_proacl - 1] = true; - tup = heap_formtuple(tupDesc, values, nulls); + tup = heap_form_tuple(tupDesc, values, nulls); simple_heap_insert(rel, tup); is_update = false; } @@ -263,326 +596,114 @@ ProcedureCreate(const char *procedureName, /* * 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(RelOid_pg_proc, retval); + deleteDependencyRecordsFor(ProcedureRelationId, retval, true); - myself.classId = RelOid_pg_proc; + myself.classId = ProcedureRelationId; myself.objectId = retval; myself.objectSubId = 0; /* dependency on namespace */ - referenced.classId = get_system_catalog_relid(NamespaceRelationName); + referenced.classId = NamespaceRelationId; referenced.objectId = procNamespace; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); /* dependency on implementation language */ - referenced.classId = get_system_catalog_relid(LanguageRelationName); + referenced.classId = LanguageRelationId; referenced.objectId = languageObjectId; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); /* dependency on return type */ - referenced.classId = RelOid_pg_type; + referenced.classId = TypeRelationId; referenced.objectId = returnType; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - /* dependency on input types */ - for (i = 0; i < parameterCount; i++) + /* dependency on parameter types */ + for (i = 0; i < allParamCount; i++) { - referenced.classId = RelOid_pg_type; - referenced.objectId = typev[i]; + referenced.classId = TypeRelationId; + referenced.objectId = allParams[i]; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } - heap_freetuple(tup); + /* dependency on parameter default expressions */ + if (parameterDefaults) + recordDependencyOnExpr(&myself, (Node *) parameterDefaults, + NIL, DEPENDENCY_NORMAL); - heap_close(rel, RowExclusiveLock); + /* dependency on owner */ + if (!is_update) + recordDependencyOnOwner(ProcedureRelationId, retval, proowner); - /* Verify function body */ - if (OidIsValid(languageValidator)) + /* dependency on any roles mentioned in ACL */ + if (!is_update && proacl != NULL) { - /* Advance command counter so new tuple can be seen by validator */ - CommandCounterIncrement(); - OidFunctionCall1(languageValidator, ObjectIdGetDatum(retval)); + int nnewmembers; + Oid *newmembers; + + nnewmembers = aclmembers(proacl, &newmembers); + updateAclDependencies(ProcedureRelationId, retval, 0, + proowner, + 0, NULL, + nnewmembers, newmembers); } - return retval; -} - + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself, is_update); -/* - * create_parameternames_array - build proargnames value from an array - * of C strings. Returns a NULL pointer if no names provided. - */ -static Datum -create_parameternames_array(int parameterCount, const char *parameterNames[]) -{ - Datum elems[FUNC_MAX_ARGS]; - bool found = false; - ArrayType *names; - int i; - - if (!parameterNames) - return PointerGetDatum(NULL); - - for (i = 0; i < parameterCount; i++) - { - const char *s = parameterNames[i]; - - if (s && *s) - found = true; - else - s = ""; - - elems[i] = DirectFunctionCall1(textin, CStringGetDatum(s)); - } - - if (!found) - return PointerGetDatum(NULL); - - names = construct_array(elems, parameterCount, TEXTOID, -1, false, 'i'); - - return PointerGetDatum(names); -} - - -/* - * check_sql_fn_retval() -- check return value of a list of sql parse trees. - * - * The return value of a sql function is the value returned by - * the final query in the function. We do some ad-hoc type checking here - * to be sure that the user is returning the type he claims. - * - * This is normally applied during function definition, but in the case - * of a function with polymorphic arguments, we instead apply it during - * function execution startup. The rettype is then the actual resolved - * output type of the function, rather than the declared type. (Therefore, - * we should never see ANYARRAY or ANYELEMENT as rettype.) - * - * The return value is true if the function returns the entire tuple result - * of its final SELECT, and false otherwise. Note that because we allow - * "SELECT rowtype_expression", this may be false even when the declared - * function return type is a rowtype. - */ -bool -check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList) -{ - Query *parse; - int cmd; - List *tlist; - ListCell *tlistitem; - int tlistlen; - Oid typerelid; - Oid restype; - Relation reln; - int relnatts; /* physical number of columns in rel */ - int rellogcols; /* # of nondeleted columns in rel */ - int colindex; /* physical column index */ - - /* guard against empty function body; OK only if void return type */ - if (queryTreeList == NIL) - { - if (rettype != VOIDOID) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("return type mismatch in function declared to return %s", - format_type_be(rettype)), - errdetail("Function's final statement must be a SELECT."))); - return false; - } - - /* find the final query */ - parse = (Query *) lfirst(list_tail(queryTreeList)); - - cmd = parse->commandType; - tlist = parse->targetList; - - /* - * The last query must be a SELECT if and only if return type isn't - * VOID. - */ - if (rettype == VOIDOID) - { - if (cmd == CMD_SELECT) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("return type mismatch in function declared to return %s", - format_type_be(rettype)), - errdetail("Function's final statement must not be a SELECT."))); - return false; - } - - /* by here, the function is declared to return some type */ - if (cmd != CMD_SELECT) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("return type mismatch in function declared to return %s", - format_type_be(rettype)), - errdetail("Function's final statement must be a SELECT."))); - - /* - * Count the non-junk entries in the result targetlist. - */ - tlistlen = ExecCleanTargetListLength(tlist); - - typerelid = typeidTypeRelid(rettype); + heap_freetuple(tup); - if (fn_typtype == 'b' || fn_typtype == 'd') - { - /* Shouldn't have a typerelid */ - Assert(typerelid == InvalidOid); + /* Post creation hook for new function */ + InvokeObjectPostCreateHook(ProcedureRelationId, retval, 0); - /* - * For base-type returns, the target list should have exactly one - * entry, and its type should agree with what the user declared. - * (As of Postgres 7.2, we accept binary-compatible types too.) - */ - if (tlistlen != 1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("return type mismatch in function declared to return %s", - format_type_be(rettype)), - errdetail("Final SELECT must return exactly one column."))); + heap_close(rel, RowExclusiveLock); - restype = ((TargetEntry *) linitial(tlist))->resdom->restype; - if (!IsBinaryCoercible(restype, rettype)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("return type mismatch in function declared to return %s", - format_type_be(rettype)), - errdetail("Actual return type is %s.", - format_type_be(restype)))); - } - else if (fn_typtype == 'c') + /* Verify function body */ + if (OidIsValid(languageValidator)) { - /* Must have a typerelid */ - Assert(typerelid != InvalidOid); + ArrayType *set_items = NULL; + int save_nestlevel = 0; - /* - * If the target list is of length 1, and the type of the varnode - * in the target list matches the declared return type, this is - * okay. This can happen, for example, where the body of the - * function is 'SELECT func2()', where func2 has the same return - * type as the function that's calling it. - */ - if (tlistlen == 1) - { - restype = ((TargetEntry *) linitial(tlist))->resdom->restype; - if (IsBinaryCoercible(restype, rettype)) - return false; /* NOT returning whole tuple */ - } + /* Advance command counter so new tuple can be seen by validator */ + CommandCounterIncrement(); /* - * Otherwise verify that the targetlist matches the return tuple - * type. This part of the typechecking is a hack. We look up the - * relation that is the declared return type, and scan the - * non-deleted attributes to ensure that they match the datatypes - * of the non-resjunk columns. + * 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. */ - reln = relation_open(typerelid, AccessShareLock); - relnatts = reln->rd_rel->relnatts; - rellogcols = 0; /* we'll count nondeleted cols as we go */ - colindex = 0; - - foreach(tlistitem, tlist) + if (check_function_bodies) { - TargetEntry *tle = (TargetEntry *) lfirst(tlistitem); - Form_pg_attribute attr; - Oid tletype; - Oid atttype; - - if (tle->resdom->resjunk) - continue; - - do + set_items = (ArrayType *) DatumGetPointer(proconfig); + if (set_items) /* Need a new GUC nesting level */ { - colindex++; - if (colindex > relnatts) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("return type mismatch in function declared to return %s", - format_type_be(rettype)), - errdetail("Final SELECT returns too many columns."))); - attr = reln->rd_att->attrs[colindex - 1]; - } while (attr->attisdropped); - rellogcols++; - - tletype = exprType((Node *) tle->expr); - atttype = attr->atttypid; - if (!IsBinaryCoercible(tletype, atttype)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("return type mismatch in function declared to return %s", - format_type_be(rettype)), - errdetail("Final SELECT returns %s instead of %s at column %d.", - format_type_be(tletype), - format_type_be(atttype), - rellogcols))); - } - - for (;;) - { - colindex++; - if (colindex > relnatts) - break; - if (!reln->rd_att->attrs[colindex - 1]->attisdropped) - rellogcols++; + save_nestlevel = NewGUCNestLevel(); + ProcessGUCArray(set_items, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION, + GUC_ACTION_SAVE); + } } - if (tlistlen != rellogcols) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("return type mismatch in function declared to return %s", - format_type_be(rettype)), - errdetail("Final SELECT returns too few columns."))); - - relation_close(reln, AccessShareLock); + OidFunctionCall1(languageValidator, ObjectIdGetDatum(retval)); - /* Report that we are returning entire tuple result */ - return true; + if (set_items) + AtEOXact_GUC(true, save_nestlevel); } - else if (rettype == RECORDOID) - { - /* - * If the target list is of length 1, and the type of the varnode - * in the target list matches the declared return type, this is - * okay. This can happen, for example, where the body of the - * function is 'SELECT func2()', where func2 has the same return - * type as the function that's calling it. - */ - if (tlistlen == 1) - { - restype = ((TargetEntry *) linitial(tlist))->resdom->restype; - if (IsBinaryCoercible(restype, rettype)) - return false; /* NOT returning whole tuple */ - } - /* - * Otherwise assume we are returning the whole tuple. - * Crosschecking against what the caller expects will happen at - * runtime. - */ - return true; - } - else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) - { - /* This should already have been caught ... */ - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("cannot determine result data type"), - errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type."))); - } - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("return type %s is not supported for SQL functions", - format_type_be(rettype)))); - - return false; + return retval; } @@ -598,27 +719,26 @@ fmgr_internal_validator(PG_FUNCTION_ARGS) { Oid funcoid = PG_GETARG_OID(0); HeapTuple tuple; - Form_pg_proc proc; bool isnull; 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. + * 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 = SearchSysCache(PROCOID, - ObjectIdGetDatum(funcoid), - 0, 0, 0); + tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcoid); - proc = (Form_pg_proc) GETSTRUCT(tuple); tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); - prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp)); + prosrc = TextDatumGetCString(tmp); if (fmgr_internal_function(prosrc) == InvalidOid) ereport(ERROR, @@ -646,35 +766,33 @@ fmgr_c_validator(PG_FUNCTION_ARGS) Oid funcoid = PG_GETARG_OID(0); void *libraryhandle; HeapTuple tuple; - Form_pg_proc proc; bool isnull; 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. + * 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 = SearchSysCache(PROCOID, - ObjectIdGetDatum(funcoid), - 0, 0, 0); + tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcoid); - proc = (Form_pg_proc) GETSTRUCT(tuple); tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull); if (isnull) - elog(ERROR, "null prosrc"); - prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp)); + elog(ERROR, "null prosrc for C function %u", funcoid); + prosrc = TextDatumGetCString(tmp); tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_probin, &isnull); if (isnull) - elog(ERROR, "null probin"); - probin = DatumGetCString(DirectFunctionCall1(textout, tmp)); + elog(ERROR, "null probin for C function %u", funcoid); + probin = TextDatumGetCString(tmp); (void) load_external_function(probin, prosrc, true, &libraryhandle); (void) fetch_finfo_record(libraryhandle, prosrc); @@ -696,51 +814,50 @@ 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; - char functyptype; bool haspolyarg; int i; - tuple = SearchSysCache(PROCOID, - ObjectIdGetDatum(funcoid), - 0, 0, 0); + 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); - functyptype = get_typtype(proc->prorettype); - /* Disallow pseudotype result */ - /* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */ - if (functyptype == 'p' && + /* except for RECORD, VOID, or polymorphic */ + if (get_typtype(proc->prorettype) == TYPTYPE_PSEUDO && proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && - proc->prorettype != ANYARRAYOID && - proc->prorettype != ANYELEMENTOID) + !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 ANYARRAY or ANYELEMENT */ + /* except for polymorphic */ haspolyarg = false; for (i = 0; i < proc->pronargs; i++) { - if (get_typtype(proc->proargtypes[i]) == 'p') + if (get_typtype(proc->proargtypes.values[i]) == TYPTYPE_PSEUDO) { - if (proc->proargtypes[i] == ANYARRAYOID || - proc->proargtypes[i] == ANYELEMENTOID) + 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[i])))); + errmsg("SQL functions cannot have arguments of type %s", + format_type_be(proc->proargtypes.values[i])))); } } @@ -751,35 +868,59 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) if (isnull) elog(ERROR, "null prosrc"); - prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp)); + 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 = tuple; + sqlerrcontext.arg = (void *) &callback_arg; sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; /* - * 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'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); + if (!haspolyarg) { - querytree_list = pg_parse_and_rewrite(prosrc, - proc->proargtypes, - proc->pronargs); - (void) check_sql_fn_retval(proc->prorettype, functyptype, - querytree_list); + /* + * 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); + + querytree_list = NIL; + foreach(lc, raw_parsetree_list) + { + Node *parsetree = (Node *) lfirst(lc); + List *querytree_sublist; + + querytree_sublist = pg_analyze_and_rewrite_params(parsetree, + prosrc, + (ParserSetupHook) sql_fn_parser_setup, + pinfo); + querytree_list = list_concat(querytree_list, + querytree_sublist); + } + + (void) check_sql_fn_retval(funcoid, proc->prorettype, + querytree_list, + NULL, NULL); } - else - querytree_list = pg_parse_query(prosrc); error_context_stack = sqlerrcontext.previous; } @@ -795,34 +936,24 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) static void sql_function_parse_error_callback(void *arg) { - HeapTuple tuple = (HeapTuple) arg; - Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tuple); - bool isnull; - Datum tmp; - char *prosrc; + parse_error_callback_arg *callback_arg = (parse_error_callback_arg *) arg; /* See if it's a syntax error; if so, transpose to CREATE FUNCTION */ - tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull); - if (isnull) - elog(ERROR, "null prosrc"); - prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp)); - - if (!function_parse_error_transpose(prosrc)) + if (!function_parse_error_transpose(callback_arg->prosrc)) { /* If it's not a syntax error, push info onto context stack */ - errcontext("SQL function \"%s\"", NameStr(proc->proname)); + errcontext("SQL function \"%s\"", callback_arg->proname); } - - pfree(prosrc); } /* * Adjust a syntax error occurring inside the function body of a CREATE - * FUNCTION command. This can be used by any function validator, 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 CREATE command; - * if we can't manage that, we set up an "internal query" syntax error instead. + * 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. */ @@ -879,8 +1010,8 @@ function_parse_error_transpose(const char *prosrc) /* * Try to locate the string literal containing the function body in the - * given text of the CREATE FUNCTION command. If successful, return the - * character (not byte) index within the command corresponding to 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 @@ -888,10 +1019,10 @@ match_prosrc_to_query(const char *prosrc, const char *queryText, int cursorpos) { /* - * Rather than fully parsing the CREATE FUNCTION 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. + * 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); @@ -907,8 +1038,8 @@ match_prosrc_to_query(const char *prosrc, const char *queryText, { /* * 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. + * 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 */ @@ -920,9 +1051,8 @@ match_prosrc_to_query(const char *prosrc, const char *queryText, cursorpos, &newcursorpos)) { /* - * Found a 'foo' match. match_prosrc_to_literal() has - * adjusted for any quotes or backslashes embedded in the - * literal. + * 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 */ @@ -974,21 +1104,27 @@ match_prosrc_to_literal(const char *prosrc, const char *literal, else if (*literal == '\'') { if (literal[1] != '\'') - return false; + goto fail; literal++; if (cursorpos > 0) newcp++; } chlen = pg_mblen(prosrc); if (strncmp(prosrc, literal, chlen) != 0) - return false; + goto fail; prosrc += chlen; literal += chlen; } - *newcursorpos = newcp; - if (*literal == '\'' && literal[1] != '\'') + { + /* success */ + *newcursorpos = newcp; return true; + } + +fail: + /* Must set *newcursorpos to suppress compiler warning */ + *newcursorpos = newcp; return false; }