* pg_proc.c
* routines to support manipulation of the pg_proc relation
*
- * Portions Copyright (c) 1996-2010, 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.170 2010/01/02 16:57:36 momjian Exp $
+ * src/backend/catalog/pg_proc.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
-#include "access/heapam.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 "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
Datum fmgr_c_validator(PG_FUNCTION_ARGS);
Datum fmgr_sql_validator(PG_FUNCTION_ARGS);
+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);
bool replace,
bool returnsSet,
Oid returnType,
+ Oid proowner,
Oid languageObjectId,
Oid languageValidator,
const char *prosrc,
bool isAgg,
bool isWindowFunc,
bool security_definer,
+ bool isLeakProof,
bool isStrict,
char volatility,
oidvector *parameterTypes,
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;
- Oid proowner = GetUserId();
Acl *proacl = NULL;
Relation rel;
HeapTuple tup;
FUNC_MAX_ARGS)));
/* note: the above is correct, we do NOT count output arguments */
+ /* Deconstruct array inputs */
if (allParameterTypes != PointerGetDatum(NULL))
{
/*
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 at least one input argument
- * is polymorphic. Also, do not allow return type INTERNAL unless at
- * least one input argument is INTERNAL.
+ * Detect whether we have polymorphic or INTERNAL arguments. The first
+ * loop checks input arguments, the second output arguments.
*/
for (i = 0; i < parameterCount; i++)
{
case ANYENUMOID:
genericInParam = true;
break;
+ case ANYRANGEOID:
+ genericInParam = true;
+ anyrangeInParam = true;
+ break;
case INTERNALOID:
internalInParam = true;
break;
{
for (i = 0; i < allParamCount; i++)
{
- /*
- * We don't bother to distinguish input and output params here, so
- * if there is, say, just an input INTERNAL param then we will
- * still set internalOutParam. This is OK since we don't really
- * care.
- */
+ if (paramModes == NULL ||
+ paramModes[i] == PROARGMODE_IN ||
+ paramModes[i] == PROARGMODE_VARIADIC)
+ continue; /* ignore input-only params */
+
switch (allParams[i])
{
case ANYARRAYOID:
case ANYENUMOID:
genericOutParam = true;
break;
+ case ANYRANGEOID:
+ genericOutParam = true;
+ anyrangeOutParam = true;
+ break;
case INTERNALOID:
internalOutParam = true;
break;
}
}
+ /*
+ * 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,
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.")));
+
if ((returnType == INTERNALOID || internalOutParam) && !internalInParam)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
procedureName,
format_type_be(parameterTypes->values[0]))));
- if (parameterModes != PointerGetDatum(NULL))
+ if (paramModes != 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);
- char *modes;
-
- 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");
- modes = (char *) ARR_DATA_PTR(modesArray);
-
/*
* Only the last input parameter can be variadic; if it is, save its
* element type. Errors here are just elog since caller should have
*/
for (i = 0; i < allParamCount; i++)
{
- switch (modes[i])
+ switch (paramModes[i])
{
case PROARGMODE_IN:
case PROARGMODE_INOUT:
}
break;
default:
- elog(ERROR, "invalid parameter mode '%c'", modes[i]);
+ elog(ERROR, "invalid parameter mode '%c'", paramModes[i]);
break;
}
}
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);
tupDesc = RelationGetDescr(rel);
/* Check for pre-existing definition */
- oldtup = SearchSysCache(PROCNAMEARGSNSP,
- PointerGetDatum(procedureName),
- PointerGetDatum(parameterTypes),
- ObjectIdGetDatum(procNamespace),
- 0);
+ oldtup = SearchSysCache3(PROCNAMEARGSNSP,
+ PointerGetDatum(procedureName),
+ PointerGetDatum(parameterTypes),
+ ObjectIdGetDatum(procNamespace));
if (HeapTupleIsValid(oldtup))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot change return type of existing function"),
- errhint("Use DROP FUNCTION first.")));
+ errhint("Use DROP FUNCTION %s first.",
+ format_procedure(HeapTupleGetOid(oldtup)))));
/*
* If it returns RECORD, check for possible change of record type
(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 first.")));
+ errhint("Use DROP FUNCTION %s first.",
+ format_procedure(HeapTupleGetOid(oldtup)))));
}
/*
* 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.
+ * 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,
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 first.")));
+ 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
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot remove parameter defaults from existing function"),
- errhint("Use DROP FUNCTION first.")));
+ errhint("Use DROP FUNCTION %s first.",
+ format_procedure(HeapTupleGetOid(oldtup)))));
proargdefaults = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup,
Anum_pg_proc_proargdefaults,
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot change data type of existing parameter default value"),
- errhint("Use DROP FUNCTION first.")));
+ errhint("Use DROP FUNCTION %s first.",
+ format_procedure(HeapTupleGetOid(oldtup)))));
newlc = lnext(newlc);
}
}
* shared dependencies do *not* need to change, and we leave them alone.)
*/
if (is_update)
- deleteDependencyRecordsFor(ProcedureRelationId, retval);
+ deleteDependencyRecordsFor(ProcedureRelationId, retval, true);
myself.classId = ProcedureRelationId;
myself.objectId = retval;
recordDependencyOn(&myself, &referenced, 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);
nnewmembers = aclmembers(proacl, &newmembers);
updateAclDependencies(ProcedureRelationId, retval, 0,
- proowner, true,
+ proowner,
0, NULL,
nnewmembers, newmembers);
}
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, is_update);
+
heap_freetuple(tup);
+ /* Post creation hook for new function */
+ InvokeObjectPostCreateHook(ProcedureRelationId, retval, 0);
+
heap_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);
}
return retval;
{
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.
*/
- 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)
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.
*/
- 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)
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;
- 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);
/*
* 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 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.values,
- proc->pronargs);
+ /*
+ * 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;
}
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 = TextDatumGetCString(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 or DO command. This can be used by any function validator or
+ * 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