1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-17 06:41:09 +03:00

Reconsider the handling of procedure OUT parameters.

Commit 2453ea142 redefined pg_proc.proargtypes to include the types of
OUT parameters, for procedures only.  While that had some advantages
for implementing the SQL-spec behavior of DROP PROCEDURE, it was pretty
disastrous from a number of other perspectives.  Notably, since the
primary key of pg_proc is name + proargtypes, this made it possible to
have multiple procedures with identical names + input arguments and
differing output argument types.  That would make it impossible to call
any one of the procedures by writing just NULL (or "?", or any other
data-type-free notation) for the output argument(s).  The change also
seems likely to cause grave confusion for client applications that
examine pg_proc and expect the traditional definition of proargtypes.

Hence, revert the definition of proargtypes to what it was, and
undo a number of complications that had been added to support that.

To support the SQL-spec behavior of DROP PROCEDURE, when there are
no argmode markers in the command's parameter list, we perform the
lookup both ways (that is, matching against both proargtypes and
proallargtypes), succeeding if we get just one unique match.
In principle this could result in ambiguous-function failures
that would not happen when using only one of the two rules.
However, overloading of procedure names is thought to be a pretty
rare usage, so this shouldn't cause many problems in practice.
Postgres-specific code such as pg_dump can defend against any
possibility of such failures by being careful to specify argmodes
for all procedure arguments.

This also fixes a few other bugs in the area of CALL statements
with named parameters, and improves the documentation a little.

catversion bump forced because the representation of procedures
with OUT arguments changes.

Discussion: https://postgr.es/m/3742981.1621533210@sss.pgh.pa.us
This commit is contained in:
Tom Lane
2021-06-10 17:11:36 -04:00
parent 3a09d75b4f
commit e56bce5d43
44 changed files with 1068 additions and 391 deletions

View File

@ -206,6 +206,7 @@ static void RemoveTempRelations(Oid tempNamespaceId);
static void RemoveTempRelationsCallback(int code, Datum arg);
static void NamespaceCallback(Datum arg, int cacheid, uint32 hashvalue);
static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
bool include_out_arguments, int pronargs,
int **argnumbers);
@ -901,6 +902,12 @@ TypeIsVisible(Oid typid)
* of additional args (which can be retrieved from the function's
* proargdefaults entry).
*
* If include_out_arguments is true, then OUT-mode arguments are considered to
* be included in the argument list. Their types are included in the returned
* arrays, and argnumbers are indexes in proallargtypes not proargtypes.
* We also set nominalnargs to be the length of proallargtypes not proargtypes.
* Otherwise OUT-mode arguments are ignored.
*
* It is not possible for nvargs and ndargs to both be nonzero in the same
* list entry, since default insertion allows matches to functions with more
* than nargs arguments while the variadic transformation requires the same
@ -911,7 +918,8 @@ TypeIsVisible(Oid typid)
* first any positional arguments, then the named arguments, then defaulted
* arguments (if needed and allowed by expand_defaults). The argnumbers[]
* array can be used to map this back to the catalog information.
* argnumbers[k] is set to the proargtypes index of the k'th call argument.
* argnumbers[k] is set to the proargtypes or proallargtypes index of the
* k'th call argument.
*
* We search a single namespace if the function name is qualified, else
* all namespaces in the search path. In the multiple-namespace case,
@ -935,13 +943,13 @@ TypeIsVisible(Oid typid)
* such an entry it should react as though the call were ambiguous.
*
* If missing_ok is true, an empty list (NULL) is returned if the name was
* schema- qualified with a schema that does not exist. Likewise if no
* schema-qualified with a schema that does not exist. Likewise if no
* candidate is found for other reasons.
*/
FuncCandidateList
FuncnameGetCandidates(List *names, int nargs, List *argnames,
bool expand_variadic, bool expand_defaults,
bool missing_ok)
bool include_out_arguments, bool missing_ok)
{
FuncCandidateList resultList = NULL;
bool any_special = false;
@ -978,6 +986,7 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
{
HeapTuple proctup = &catlist->members[i]->tuple;
Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
Oid *proargtypes = procform->proargtypes.values;
int pronargs = procform->pronargs;
int effective_nargs;
int pathpos = 0;
@ -1012,6 +1021,35 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
continue; /* proc is not in search path */
}
/*
* If we are asked to match to OUT arguments, then use the
* proallargtypes array (which includes those); otherwise use
* proargtypes (which doesn't). Of course, if proallargtypes is null,
* we always use proargtypes.
*/
if (include_out_arguments)
{
Datum proallargtypes;
bool isNull;
proallargtypes = SysCacheGetAttr(PROCNAMEARGSNSP, proctup,
Anum_pg_proc_proallargtypes,
&isNull);
if (!isNull)
{
ArrayType *arr = DatumGetArrayTypeP(proallargtypes);
pronargs = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
pronargs < 0 ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls");
Assert(pronargs >= procform->pronargs);
proargtypes = (Oid *) ARR_DATA_PTR(arr);
}
}
if (argnames != NIL)
{
/*
@ -1047,6 +1085,7 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
/* Check for argument name match, generate positional mapping */
if (!MatchNamedCall(proctup, nargs, argnames,
include_out_arguments, pronargs,
&argnumbers))
continue;
@ -1105,12 +1144,12 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
effective_nargs * sizeof(Oid));
newResult->pathpos = pathpos;
newResult->oid = procform->oid;
newResult->nominalnargs = pronargs;
newResult->nargs = effective_nargs;
newResult->argnumbers = argnumbers;
if (argnumbers)
{
/* Re-order the argument types into call's logical order */
Oid *proargtypes = procform->proargtypes.values;
int i;
for (i = 0; i < pronargs; i++)
@ -1119,8 +1158,7 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
else
{
/* Simple positional case, just copy proargtypes as-is */
memcpy(newResult->args, procform->proargtypes.values,
pronargs * sizeof(Oid));
memcpy(newResult->args, proargtypes, pronargs * sizeof(Oid));
}
if (variadic)
{
@ -1293,6 +1331,10 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
* the function, in positions after the last positional argument, and there
* are defaults for all unsupplied arguments.
*
* If include_out_arguments is true, we are treating OUT arguments as
* included in the argument list. pronargs is the number of arguments
* we're considering (the length of either proargtypes or proallargtypes).
*
* The number of positional arguments is nargs - list_length(argnames).
* Note caller has already done basic checks on argument count.
*
@ -1303,10 +1345,10 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
*/
static bool
MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
bool include_out_arguments, int pronargs,
int **argnumbers)
{
Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
int pronargs = procform->pronargs;
int numposargs = nargs - list_length(argnames);
int pronallargs;
Oid *p_argtypes;
@ -1333,6 +1375,8 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
&p_argtypes, &p_argnames, &p_argmodes);
Assert(p_argnames != NULL);
Assert(include_out_arguments ? (pronargs == pronallargs) : (pronargs <= pronallargs));
/* initialize state for matching */
*argnumbers = (int *) palloc(pronargs * sizeof(int));
memset(arggiven, false, pronargs * sizeof(bool));
@ -1355,8 +1399,9 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
found = false;
for (i = 0; i < pronallargs; i++)
{
/* consider only input parameters */
if (p_argmodes &&
/* consider only input params, except with include_out_arguments */
if (!include_out_arguments &&
p_argmodes &&
(p_argmodes[i] != FUNC_PARAM_IN &&
p_argmodes[i] != FUNC_PARAM_INOUT &&
p_argmodes[i] != FUNC_PARAM_VARIADIC))
@ -1371,7 +1416,7 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
found = true;
break;
}
/* increase pp only for input parameters */
/* increase pp only for considered parameters */
pp++;
}
/* if name isn't in proargnames, fail */
@ -1448,7 +1493,7 @@ FunctionIsVisible(Oid funcid)
visible = false;
clist = FuncnameGetCandidates(list_make1(makeString(proname)),
nargs, NIL, false, false, false);
nargs, NIL, false, false, false, false);
for (; clist; clist = clist->next)
{
@ -1721,6 +1766,7 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
newResult->pathpos = pathpos;
newResult->oid = operform->oid;
newResult->nominalnargs = 2;
newResult->nargs = 2;
newResult->nvargs = 0;
newResult->ndargs = 0;

View File

@ -846,7 +846,7 @@ lookup_agg_function(List *fnName,
* the function.
*/
fdresult = func_get_detail(fnName, NIL, NIL,
nargs, input_types, false, false,
nargs, input_types, false, false, false,
&fnOid, rettype, &retset,
&nvargs, &vatype,
&true_oid_array, NULL);

View File

@ -471,12 +471,10 @@ ProcedureCreate(const char *procedureName,
if (isnull)
proargmodes = PointerGetDatum(NULL); /* just to be sure */
n_old_arg_names = get_func_input_arg_names(prokind,
proargnames,
n_old_arg_names = get_func_input_arg_names(proargnames,
proargmodes,
&old_arg_names);
n_new_arg_names = get_func_input_arg_names(prokind,
parameterNames,
n_new_arg_names = get_func_input_arg_names(parameterNames,
parameterModes,
&new_arg_names);
for (j = 0; j < n_old_arg_names; j++)

View File

@ -169,16 +169,16 @@ compute_return_type(TypeName *returnType, Oid languageOid,
}
/*
* Interpret the function parameter list of a CREATE FUNCTION or
* CREATE AGGREGATE statement.
* Interpret the function parameter list of a CREATE FUNCTION,
* CREATE PROCEDURE, or CREATE AGGREGATE statement.
*
* Input parameters:
* parameters: list of FunctionParameter structs
* languageOid: OID of function language (InvalidOid if it's CREATE AGGREGATE)
* objtype: needed only to determine error handling and required result type
* objtype: identifies type of object being created
*
* Results are stored into output parameters. parameterTypes must always
* be created, but the other arrays are set to NULL if not needed.
* be created, but the other arrays/lists can be NULL pointers if not needed.
* variadicArgType is set to the variadic array type if there's a VARIADIC
* parameter (there can be only one); or to InvalidOid if not.
* requiredResultType is set to InvalidOid if there are no OUT parameters,
@ -200,8 +200,8 @@ interpret_function_parameter_list(ParseState *pstate,
Oid *requiredResultType)
{
int parameterCount = list_length(parameters);
Oid *sigArgTypes;
int sigArgCount = 0;
Oid *inTypes;
int inCount = 0;
Datum *allTypes;
Datum *paramModes;
Datum *paramNames;
@ -215,7 +215,7 @@ interpret_function_parameter_list(ParseState *pstate,
*variadicArgType = InvalidOid; /* default result */
*requiredResultType = InvalidOid; /* default result */
sigArgTypes = (Oid *) palloc(parameterCount * sizeof(Oid));
inTypes = (Oid *) palloc(parameterCount * sizeof(Oid));
allTypes = (Datum *) palloc(parameterCount * sizeof(Datum));
paramModes = (Datum *) palloc(parameterCount * sizeof(Datum));
paramNames = (Datum *) palloc0(parameterCount * sizeof(Datum));
@ -227,11 +227,16 @@ interpret_function_parameter_list(ParseState *pstate,
{
FunctionParameter *fp = (FunctionParameter *) lfirst(x);
TypeName *t = fp->argType;
FunctionParameterMode fpmode = fp->mode;
bool isinput = false;
Oid toid;
Type typtup;
AclResult aclresult;
/* For our purposes here, a defaulted mode spec is identical to IN */
if (fpmode == FUNC_PARAM_DEFAULT)
fpmode = FUNC_PARAM_IN;
typtup = LookupTypeName(NULL, t, NULL, false);
if (typtup)
{
@ -288,37 +293,42 @@ interpret_function_parameter_list(ParseState *pstate,
}
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
if (fpmode != FUNC_PARAM_OUT && fpmode != FUNC_PARAM_TABLE)
{
/* other input parameters can't follow a VARIADIC parameter */
if (varCount > 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("VARIADIC parameter must be the last input parameter")));
inTypes[inCount++] = toid;
isinput = true;
if (parameterTypes_list)
*parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
}
/* handle signature parameters */
if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
(objtype == OBJECT_PROCEDURE && fp->mode == FUNC_PARAM_OUT) ||
fp->mode == FUNC_PARAM_VARIADIC)
{
/* other signature parameters can't follow a VARIADIC parameter */
if (varCount > 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("VARIADIC parameter must be the last signature parameter")));
sigArgTypes[sigArgCount++] = toid;
}
/* handle output parameters */
if (fp->mode != FUNC_PARAM_IN && fp->mode != FUNC_PARAM_VARIADIC)
if (fpmode != FUNC_PARAM_IN && fpmode != FUNC_PARAM_VARIADIC)
{
if (objtype == OBJECT_PROCEDURE)
{
/*
* We disallow OUT-after-VARIADIC only for procedures. While
* such a case causes no confusion in ordinary function calls,
* it would cause confusion in a CALL statement.
*/
if (varCount > 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("VARIADIC parameter must be the last parameter")));
/* Procedures with output parameters always return RECORD */
*requiredResultType = RECORDOID;
}
else if (outCount == 0) /* save first output param's type */
*requiredResultType = toid;
outCount++;
}
if (fp->mode == FUNC_PARAM_VARIADIC)
if (fpmode == FUNC_PARAM_VARIADIC)
{
*variadicArgType = toid;
varCount++;
@ -341,7 +351,7 @@ interpret_function_parameter_list(ParseState *pstate,
allTypes[i] = ObjectIdGetDatum(toid);
paramModes[i] = CharGetDatum(fp->mode);
paramModes[i] = CharGetDatum(fpmode);
if (fp->name && fp->name[0])
{
@ -356,19 +366,24 @@ interpret_function_parameter_list(ParseState *pstate,
foreach(px, parameters)
{
FunctionParameter *prevfp = (FunctionParameter *) lfirst(px);
FunctionParameterMode prevfpmode;
if (prevfp == fp)
break;
/* as above, default mode is IN */
prevfpmode = prevfp->mode;
if (prevfpmode == FUNC_PARAM_DEFAULT)
prevfpmode = FUNC_PARAM_IN;
/* pure in doesn't conflict with pure out */
if ((fp->mode == FUNC_PARAM_IN ||
fp->mode == FUNC_PARAM_VARIADIC) &&
(prevfp->mode == FUNC_PARAM_OUT ||
prevfp->mode == FUNC_PARAM_TABLE))
if ((fpmode == FUNC_PARAM_IN ||
fpmode == FUNC_PARAM_VARIADIC) &&
(prevfpmode == FUNC_PARAM_OUT ||
prevfpmode == FUNC_PARAM_TABLE))
continue;
if ((prevfp->mode == FUNC_PARAM_IN ||
prevfp->mode == FUNC_PARAM_VARIADIC) &&
(fp->mode == FUNC_PARAM_OUT ||
fp->mode == FUNC_PARAM_TABLE))
if ((prevfpmode == FUNC_PARAM_IN ||
prevfpmode == FUNC_PARAM_VARIADIC) &&
(fpmode == FUNC_PARAM_OUT ||
fpmode == FUNC_PARAM_TABLE))
continue;
if (prevfp->name && prevfp->name[0] &&
strcmp(prevfp->name, fp->name) == 0)
@ -432,13 +447,23 @@ interpret_function_parameter_list(ParseState *pstate,
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("input parameters after one with a default value must also have defaults")));
/*
* For procedures, we also can't allow OUT parameters after one
* with a default, because the same sort of confusion arises in a
* CALL statement.
*/
if (objtype == OBJECT_PROCEDURE && have_defaults)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("procedure OUT parameters cannot appear after one with a default value")));
}
i++;
}
/* Now construct the proper outputs as needed */
*parameterTypes = buildoidvector(sigArgTypes, sigArgCount);
*parameterTypes = buildoidvector(inTypes, inCount);
if (outCount > 0 || varCount > 0)
{
@ -2179,9 +2204,6 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
int nargs;
int i;
AclResult aclresult;
Oid *argtypes;
char **argnames;
char *argmodes;
FmgrInfo flinfo;
CallContext *callcontext;
EState *estate;
@ -2224,29 +2246,10 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
if (((Form_pg_proc) GETSTRUCT(tp))->prosecdef)
callcontext->atomic = true;
/*
* Expand named arguments, defaults, etc. We do not want to scribble on
* the passed-in CallStmt parse tree, so first flat-copy fexpr, allowing
* us to replace its args field. (Note that expand_function_arguments
* will not modify any of the passed-in data structure.)
*/
{
FuncExpr *nexpr = makeNode(FuncExpr);
memcpy(nexpr, fexpr, sizeof(FuncExpr));
fexpr = nexpr;
}
fexpr->args = expand_function_arguments(fexpr->args,
fexpr->funcresulttype,
tp);
nargs = list_length(fexpr->args);
get_func_arg_info(tp, &argtypes, &argnames, &argmodes);
ReleaseSysCache(tp);
/* safety check; see ExecInitFunc() */
nargs = list_length(fexpr->args);
if (nargs > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
@ -2273,24 +2276,16 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
i = 0;
foreach(lc, fexpr->args)
{
if (argmodes && argmodes[i] == PROARGMODE_OUT)
{
fcinfo->args[i].value = 0;
fcinfo->args[i].isnull = true;
}
else
{
ExprState *exprstate;
Datum val;
bool isnull;
ExprState *exprstate;
Datum val;
bool isnull;
exprstate = ExecPrepareExpr(lfirst(lc), estate);
exprstate = ExecPrepareExpr(lfirst(lc), estate);
val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull);
val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull);
fcinfo->args[i].value = val;
fcinfo->args[i].isnull = isnull;
}
fcinfo->args[i].value = val;
fcinfo->args[i].isnull = isnull;
i++;
}

View File

@ -245,8 +245,7 @@ prepare_sql_fn_parse_info(HeapTuple procedureTuple,
if (isNull)
proargmodes = PointerGetDatum(NULL); /* just to be sure */
n_arg_names = get_func_input_arg_names(procedureStruct->prokind,
proargnames, proargmodes,
n_arg_names = get_func_input_arg_names(proargnames, proargmodes,
&pinfo->argnames);
/* Paranoia: ignore the result if too few array entries */
@ -1536,7 +1535,7 @@ check_sql_fn_statements(List *queryTreeLists)
Query *query = lfirst_node(Query, lc2);
/*
* Disallow procedures with output arguments. The current
* Disallow calling procedures with output arguments. The current
* implementation would just throw the output values away, unless
* the statement is the last one. Per SQL standard, we should
* assign the output values by name. By disallowing this here, we
@ -1545,31 +1544,12 @@ check_sql_fn_statements(List *queryTreeLists)
if (query->commandType == CMD_UTILITY &&
IsA(query->utilityStmt, CallStmt))
{
CallStmt *stmt = castNode(CallStmt, query->utilityStmt);
HeapTuple tuple;
int numargs;
Oid *argtypes;
char **argnames;
char *argmodes;
int i;
CallStmt *stmt = (CallStmt *) query->utilityStmt;
tuple = SearchSysCache1(PROCOID,
ObjectIdGetDatum(stmt->funcexpr->funcid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u",
stmt->funcexpr->funcid);
numargs = get_func_arg_info(tuple,
&argtypes, &argnames, &argmodes);
ReleaseSysCache(tuple);
for (i = 0; i < numargs; i++)
{
if (argmodes && (argmodes[i] == PROARGMODE_INOUT ||
argmodes[i] == PROARGMODE_OUT))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("calling procedures with output arguments is not supported in SQL functions")));
}
if (stmt->outargs != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("calling procedures with output arguments is not supported in SQL functions")));
}
}
}

View File

@ -3407,6 +3407,7 @@ _copyObjectWithArgs(const ObjectWithArgs *from)
COPY_NODE_FIELD(objname);
COPY_NODE_FIELD(objargs);
COPY_NODE_FIELD(objfuncargs);
COPY_SCALAR_FIELD(args_unspecified);
return newnode;
@ -3478,6 +3479,7 @@ _copyCallStmt(const CallStmt *from)
COPY_NODE_FIELD(funccall);
COPY_NODE_FIELD(funcexpr);
COPY_NODE_FIELD(outargs);
return newnode;
}

View File

@ -1182,6 +1182,7 @@ _equalObjectWithArgs(const ObjectWithArgs *a, const ObjectWithArgs *b)
{
COMPARE_NODE_FIELD(objname);
COMPARE_NODE_FIELD(objargs);
COMPARE_NODE_FIELD(objfuncargs);
COMPARE_SCALAR_FIELD(args_unspecified);
return true;
@ -1241,6 +1242,7 @@ _equalCallStmt(const CallStmt *a, const CallStmt *b)
{
COMPARE_NODE_FIELD(funccall);
COMPARE_NODE_FIELD(funcexpr);
COMPARE_NODE_FIELD(outargs);
return true;
}

View File

@ -124,10 +124,13 @@ static Expr *simplify_function(Oid funcid,
Oid result_collid, Oid input_collid, List **args_p,
bool funcvariadic, bool process_args, bool allow_non_const,
eval_const_expressions_context *context);
static List *reorder_function_arguments(List *args, HeapTuple func_tuple);
static List *add_function_defaults(List *args, HeapTuple func_tuple);
static List *reorder_function_arguments(List *args, int pronargs,
HeapTuple func_tuple);
static List *add_function_defaults(List *args, int pronargs,
HeapTuple func_tuple);
static List *fetch_function_defaults(HeapTuple func_tuple);
static void recheck_cast_function_args(List *args, Oid result_type,
Oid *proargtypes, int pronargs,
HeapTuple func_tuple);
static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
@ -2326,7 +2329,8 @@ eval_const_expressions_mutator(Node *node,
if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u", funcid);
args = expand_function_arguments(expr->args, expr->wintype,
args = expand_function_arguments(expr->args,
false, expr->wintype,
func_tuple);
ReleaseSysCache(func_tuple);
@ -3841,7 +3845,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
*/
if (process_args)
{
args = expand_function_arguments(args, result_type, func_tuple);
args = expand_function_arguments(args, false, result_type, func_tuple);
args = (List *) expression_tree_mutator((Node *) args,
eval_const_expressions_mutator,
(void *) context);
@ -3905,6 +3909,15 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
* expand_function_arguments: convert named-notation args to positional args
* and/or insert default args, as needed
*
* Returns a possibly-transformed version of the args list.
*
* If include_out_arguments is true, then the args list and the result
* include OUT arguments.
*
* The expected result type of the call must be given, for sanity-checking
* purposes. Also, we ask the caller to provide the function's actual
* pg_proc tuple, not just its OID.
*
* If we need to change anything, the input argument list is copied, not
* modified.
*
@ -3913,12 +3926,46 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
* will fall through very quickly if there's nothing to do.
*/
List *
expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
expand_function_arguments(List *args, bool include_out_arguments,
Oid result_type, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
Oid *proargtypes = funcform->proargtypes.values;
int pronargs = funcform->pronargs;
bool has_named_args = false;
ListCell *lc;
/*
* If we are asked to match to OUT arguments, then use the proallargtypes
* array (which includes those); otherwise use proargtypes (which
* doesn't). Of course, if proallargtypes is null, we always use
* proargtypes. (Fetching proallargtypes is annoyingly expensive
* considering that we may have nothing to do here, but fortunately the
* common case is include_out_arguments == false.)
*/
if (include_out_arguments)
{
Datum proallargtypes;
bool isNull;
proallargtypes = SysCacheGetAttr(PROCOID, func_tuple,
Anum_pg_proc_proallargtypes,
&isNull);
if (!isNull)
{
ArrayType *arr = DatumGetArrayTypeP(proallargtypes);
pronargs = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
pronargs < 0 ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls");
Assert(pronargs >= funcform->pronargs);
proargtypes = (Oid *) ARR_DATA_PTR(arr);
}
}
/* Do we have any named arguments? */
foreach(lc, args)
{
@ -3934,16 +3981,20 @@ expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
/* If so, we must apply reorder_function_arguments */
if (has_named_args)
{
args = reorder_function_arguments(args, func_tuple);
args = reorder_function_arguments(args, pronargs, func_tuple);
/* Recheck argument types and add casts if needed */
recheck_cast_function_args(args, result_type, func_tuple);
recheck_cast_function_args(args, result_type,
proargtypes, pronargs,
func_tuple);
}
else if (list_length(args) < funcform->pronargs)
else if (list_length(args) < pronargs)
{
/* No named args, but we seem to be short some defaults */
args = add_function_defaults(args, func_tuple);
args = add_function_defaults(args, pronargs, func_tuple);
/* Recheck argument types and add casts if needed */
recheck_cast_function_args(args, result_type, func_tuple);
recheck_cast_function_args(args, result_type,
proargtypes, pronargs,
func_tuple);
}
return args;
@ -3956,10 +4007,9 @@ expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
* impossible to form a truly valid positional call without that.
*/
static List *
reorder_function_arguments(List *args, HeapTuple func_tuple)
reorder_function_arguments(List *args, int pronargs, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int pronargs = funcform->pronargs;
int nargsprovided = list_length(args);
Node *argarray[FUNC_MAX_ARGS];
ListCell *lc;
@ -3986,6 +4036,7 @@ reorder_function_arguments(List *args, HeapTuple func_tuple)
{
NamedArgExpr *na = (NamedArgExpr *) arg;
Assert(na->argnumber >= 0 && na->argnumber < pronargs);
Assert(argarray[na->argnumber] == NULL);
argarray[na->argnumber] = (Node *) na->arg;
}
@ -4026,9 +4077,8 @@ reorder_function_arguments(List *args, HeapTuple func_tuple)
* and so we know we just need to add defaults at the end.
*/
static List *
add_function_defaults(List *args, HeapTuple func_tuple)
add_function_defaults(List *args, int pronargs, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int nargsprovided = list_length(args);
List *defaults;
int ndelete;
@ -4037,7 +4087,7 @@ add_function_defaults(List *args, HeapTuple func_tuple)
defaults = fetch_function_defaults(func_tuple);
/* Delete any unused defaults from the list */
ndelete = nargsprovided + list_length(defaults) - funcform->pronargs;
ndelete = nargsprovided + list_length(defaults) - pronargs;
if (ndelete < 0)
elog(ERROR, "not enough default arguments");
if (ndelete > 0)
@ -4086,7 +4136,9 @@ fetch_function_defaults(HeapTuple func_tuple)
* caller should have already copied the list structure.
*/
static void
recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
recheck_cast_function_args(List *args, Oid result_type,
Oid *proargtypes, int pronargs,
HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int nargs;
@ -4102,9 +4154,8 @@ recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
{
actual_arg_types[nargs++] = exprType((Node *) lfirst(lc));
}
Assert(nargs == funcform->pronargs);
memcpy(declared_arg_types, funcform->proargtypes.values,
funcform->pronargs * sizeof(Oid));
Assert(nargs == pronargs);
memcpy(declared_arg_types, proargtypes, pronargs * sizeof(Oid));
rettype = enforce_generic_type_consistency(actual_arg_types,
declared_arg_types,
nargs,

View File

@ -25,6 +25,7 @@
#include "postgres.h"
#include "access/sysattr.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@ -50,6 +51,7 @@
#include "utils/guc.h"
#include "utils/queryjumble.h"
#include "utils/rel.h"
#include "utils/syscache.h"
/* Hook for plugins to get control at end of parse analysis */
@ -2933,8 +2935,6 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
/*
* transform a CallStmt
*
* We need to do parse analysis on the procedure call and its arguments.
*/
static Query *
transformCallStmt(ParseState *pstate, CallStmt *stmt)
@ -2942,8 +2942,17 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
List *targs;
ListCell *lc;
Node *node;
FuncExpr *fexpr;
HeapTuple proctup;
Datum proargmodes;
bool isNull;
List *outargs = NIL;
Query *result;
/*
* First, do standard parse analysis on the procedure call and its
* arguments, allowing us to identify the called procedure.
*/
targs = NIL;
foreach(lc, stmt->funccall->args)
{
@ -2962,8 +2971,85 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt)
assign_expr_collations(pstate, node);
stmt->funcexpr = castNode(FuncExpr, node);
fexpr = castNode(FuncExpr, node);
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid));
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
/*
* Expand the argument list to deal with named-argument notation and
* default arguments. For ordinary FuncExprs this'd be done during
* planning, but a CallStmt doesn't go through planning, and there seems
* no good reason not to do it here.
*/
fexpr->args = expand_function_arguments(fexpr->args,
true,
fexpr->funcresulttype,
proctup);
/* Fetch proargmodes; if it's null, there are no output args */
proargmodes = SysCacheGetAttr(PROCOID, proctup,
Anum_pg_proc_proargmodes,
&isNull);
if (!isNull)
{
/*
* Split the list into input arguments in fexpr->args and output
* arguments in stmt->outargs. INOUT arguments appear in both lists.
*/
ArrayType *arr;
int numargs;
char *argmodes;
List *inargs;
int i;
arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */
numargs = list_length(fexpr->args);
if (ARR_NDIM(arr) != 1 ||
ARR_DIMS(arr)[0] != numargs ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != CHAROID)
elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls",
numargs);
argmodes = (char *) ARR_DATA_PTR(arr);
inargs = NIL;
i = 0;
foreach(lc, fexpr->args)
{
Node *n = lfirst(lc);
switch (argmodes[i])
{
case PROARGMODE_IN:
case PROARGMODE_VARIADIC:
inargs = lappend(inargs, n);
break;
case PROARGMODE_OUT:
outargs = lappend(outargs, n);
break;
case PROARGMODE_INOUT:
inargs = lappend(inargs, n);
outargs = lappend(outargs, copyObject(n));
break;
default:
/* note we don't support PROARGMODE_TABLE */
elog(ERROR, "invalid argmode %c for procedure",
argmodes[i]);
break;
}
i++;
}
fexpr->args = inargs;
}
stmt->funcexpr = fexpr;
stmt->outargs = outargs;
ReleaseSysCache(proctup);
/* represent the command as a utility Query */
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *) stmt;

View File

@ -172,7 +172,7 @@ static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
static List *extractArgTypes(ObjectType objtype, List *parameters);
static List *extractArgTypes(List *parameters);
static List *extractAggrArgTypes(List *aggrargs);
static List *makeOrderedSetArgs(List *directargs, List *orderedargs,
core_yyscan_t yyscanner);
@ -385,8 +385,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <accesspriv> privilege
%type <list> privileges privilege_list
%type <privtarget> privilege_target
%type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes procedure_with_argtypes function_with_argtypes_common
%type <list> function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list procedure_with_argtypes_list
%type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes
%type <list> function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list
%type <ival> defacl_privilege_target
%type <defelt> DefACLOption
%type <list> DefACLOptionList
@ -4757,7 +4757,7 @@ AlterExtensionContentsStmt:
n->object = (Node *) lcons(makeString($9), $7);
$$ = (Node *)n;
}
| ALTER EXTENSION name add_drop PROCEDURE procedure_with_argtypes
| ALTER EXTENSION name add_drop PROCEDURE function_with_argtypes
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
n->extname = $3;
@ -4766,7 +4766,7 @@ AlterExtensionContentsStmt:
n->object = (Node *) $6;
$$ = (Node *)n;
}
| ALTER EXTENSION name add_drop ROUTINE procedure_with_argtypes
| ALTER EXTENSION name add_drop ROUTINE function_with_argtypes
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
n->extname = $3;
@ -6505,7 +6505,7 @@ CommentStmt:
n->comment = $8;
$$ = (Node *) n;
}
| COMMENT ON PROCEDURE procedure_with_argtypes IS comment_text
| COMMENT ON PROCEDURE function_with_argtypes IS comment_text
{
CommentStmt *n = makeNode(CommentStmt);
n->objtype = OBJECT_PROCEDURE;
@ -6513,7 +6513,7 @@ CommentStmt:
n->comment = $6;
$$ = (Node *) n;
}
| COMMENT ON ROUTINE procedure_with_argtypes IS comment_text
| COMMENT ON ROUTINE function_with_argtypes IS comment_text
{
CommentStmt *n = makeNode(CommentStmt);
n->objtype = OBJECT_ROUTINE;
@ -6659,7 +6659,7 @@ SecLabelStmt:
n->label = $9;
$$ = (Node *) n;
}
| SECURITY LABEL opt_provider ON PROCEDURE procedure_with_argtypes
| SECURITY LABEL opt_provider ON PROCEDURE function_with_argtypes
IS security_label
{
SecLabelStmt *n = makeNode(SecLabelStmt);
@ -7023,7 +7023,7 @@ privilege_target:
n->objs = $2;
$$ = n;
}
| PROCEDURE procedure_with_argtypes_list
| PROCEDURE function_with_argtypes_list
{
PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->targtype = ACL_TARGET_OBJECT;
@ -7031,7 +7031,7 @@ privilege_target:
n->objs = $2;
$$ = n;
}
| ROUTINE procedure_with_argtypes_list
| ROUTINE function_with_argtypes_list
{
PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->targtype = ACL_TARGET_OBJECT;
@ -7556,33 +7556,21 @@ function_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
procedure_with_argtypes_list:
procedure_with_argtypes { $$ = list_make1($1); }
| procedure_with_argtypes_list ',' procedure_with_argtypes
{ $$ = lappend($1, $3); }
;
function_with_argtypes:
func_name func_args
{
ObjectWithArgs *n = makeNode(ObjectWithArgs);
n->objname = $1;
n->objargs = extractArgTypes(OBJECT_FUNCTION, $2);
n->objargs = extractArgTypes($2);
n->objfuncargs = $2;
$$ = n;
}
| function_with_argtypes_common
{
$$ = $1;
}
;
function_with_argtypes_common:
/*
* Because of reduce/reduce conflicts, we can't use func_name
* below, but we can write it out the long way, which actually
* allows more cases.
*/
type_func_name_keyword
| type_func_name_keyword
{
ObjectWithArgs *n = makeNode(ObjectWithArgs);
n->objname = list_make1(makeString(pstrdup($1)));
@ -7606,24 +7594,6 @@ function_with_argtypes_common:
}
;
/*
* This is different from function_with_argtypes in the call to
* extractArgTypes().
*/
procedure_with_argtypes:
func_name func_args
{
ObjectWithArgs *n = makeNode(ObjectWithArgs);
n->objname = $1;
n->objargs = extractArgTypes(OBJECT_PROCEDURE, $2);
$$ = n;
}
| function_with_argtypes_common
{
$$ = $1;
}
;
/*
* func_args_with_defaults is separate because we only want to accept
* defaults in CREATE FUNCTION, not in ALTER etc.
@ -7673,7 +7643,7 @@ func_arg:
FunctionParameter *n = makeNode(FunctionParameter);
n->name = $1;
n->argType = $2;
n->mode = FUNC_PARAM_IN;
n->mode = FUNC_PARAM_DEFAULT;
n->defexpr = NULL;
$$ = n;
}
@ -7691,7 +7661,7 @@ func_arg:
FunctionParameter *n = makeNode(FunctionParameter);
n->name = NULL;
n->argType = $1;
n->mode = FUNC_PARAM_IN;
n->mode = FUNC_PARAM_DEFAULT;
n->defexpr = NULL;
$$ = n;
}
@ -7763,7 +7733,8 @@ func_arg_with_default:
/* Aggregate args can be most things that function args can be */
aggr_arg: func_arg
{
if (!($1->mode == FUNC_PARAM_IN ||
if (!($1->mode == FUNC_PARAM_DEFAULT ||
$1->mode == FUNC_PARAM_IN ||
$1->mode == FUNC_PARAM_VARIADIC))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@ -7832,6 +7803,7 @@ aggregate_with_argtypes:
ObjectWithArgs *n = makeNode(ObjectWithArgs);
n->objname = $1;
n->objargs = extractAggrArgTypes($2);
n->objfuncargs = (List *) linitial($2);
$$ = n;
}
;
@ -8056,7 +8028,7 @@ AlterFunctionStmt:
n->actions = $4;
$$ = (Node *) n;
}
| ALTER PROCEDURE procedure_with_argtypes alterfunc_opt_list opt_restrict
| ALTER PROCEDURE function_with_argtypes alterfunc_opt_list opt_restrict
{
AlterFunctionStmt *n = makeNode(AlterFunctionStmt);
n->objtype = OBJECT_PROCEDURE;
@ -8064,7 +8036,7 @@ AlterFunctionStmt:
n->actions = $4;
$$ = (Node *) n;
}
| ALTER ROUTINE procedure_with_argtypes alterfunc_opt_list opt_restrict
| ALTER ROUTINE function_with_argtypes alterfunc_opt_list opt_restrict
{
AlterFunctionStmt *n = makeNode(AlterFunctionStmt);
n->objtype = OBJECT_ROUTINE;
@ -8120,7 +8092,7 @@ RemoveFuncStmt:
n->concurrent = false;
$$ = (Node *)n;
}
| DROP PROCEDURE procedure_with_argtypes_list opt_drop_behavior
| DROP PROCEDURE function_with_argtypes_list opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_PROCEDURE;
@ -8130,7 +8102,7 @@ RemoveFuncStmt:
n->concurrent = false;
$$ = (Node *)n;
}
| DROP PROCEDURE IF_P EXISTS procedure_with_argtypes_list opt_drop_behavior
| DROP PROCEDURE IF_P EXISTS function_with_argtypes_list opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_PROCEDURE;
@ -8140,7 +8112,7 @@ RemoveFuncStmt:
n->concurrent = false;
$$ = (Node *)n;
}
| DROP ROUTINE procedure_with_argtypes_list opt_drop_behavior
| DROP ROUTINE function_with_argtypes_list opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_ROUTINE;
@ -8150,7 +8122,7 @@ RemoveFuncStmt:
n->concurrent = false;
$$ = (Node *)n;
}
| DROP ROUTINE IF_P EXISTS procedure_with_argtypes_list opt_drop_behavior
| DROP ROUTINE IF_P EXISTS function_with_argtypes_list opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_ROUTINE;
@ -8622,7 +8594,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
n->missing_ok = true;
$$ = (Node *)n;
}
| ALTER PROCEDURE procedure_with_argtypes RENAME TO name
| ALTER PROCEDURE function_with_argtypes RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_PROCEDURE;
@ -8640,7 +8612,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
n->missing_ok = false;
$$ = (Node *)n;
}
| ALTER ROUTINE procedure_with_argtypes RENAME TO name
| ALTER ROUTINE function_with_argtypes RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_ROUTINE;
@ -9051,7 +9023,7 @@ AlterObjectDependsStmt:
n->remove = $4;
$$ = (Node *)n;
}
| ALTER PROCEDURE procedure_with_argtypes opt_no DEPENDS ON EXTENSION name
| ALTER PROCEDURE function_with_argtypes opt_no DEPENDS ON EXTENSION name
{
AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
n->objectType = OBJECT_PROCEDURE;
@ -9060,7 +9032,7 @@ AlterObjectDependsStmt:
n->remove = $4;
$$ = (Node *)n;
}
| ALTER ROUTINE procedure_with_argtypes opt_no DEPENDS ON EXTENSION name
| ALTER ROUTINE function_with_argtypes opt_no DEPENDS ON EXTENSION name
{
AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
n->objectType = OBJECT_ROUTINE;
@ -9191,7 +9163,7 @@ AlterObjectSchemaStmt:
n->missing_ok = false;
$$ = (Node *)n;
}
| ALTER PROCEDURE procedure_with_argtypes SET SCHEMA name
| ALTER PROCEDURE function_with_argtypes SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
n->objectType = OBJECT_PROCEDURE;
@ -9200,7 +9172,7 @@ AlterObjectSchemaStmt:
n->missing_ok = false;
$$ = (Node *)n;
}
| ALTER ROUTINE procedure_with_argtypes SET SCHEMA name
| ALTER ROUTINE function_with_argtypes SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
n->objectType = OBJECT_ROUTINE;
@ -9502,7 +9474,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
n->newowner = $9;
$$ = (Node *)n;
}
| ALTER PROCEDURE procedure_with_argtypes OWNER TO RoleSpec
| ALTER PROCEDURE function_with_argtypes OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_PROCEDURE;
@ -9510,7 +9482,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
n->newowner = $6;
$$ = (Node *)n;
}
| ALTER ROUTINE procedure_with_argtypes OWNER TO RoleSpec
| ALTER ROUTINE function_with_argtypes OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
n->objectType = OBJECT_ROUTINE;
@ -16698,14 +16670,13 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
}
/* extractArgTypes()
*
* Given a list of FunctionParameter nodes, extract a list of just the
* argument types (TypeNames) for signature parameters only (e.g., only input
* parameters for functions). This is what is needed to look up an existing
* function, which is what is wanted by the productions that use this call.
* argument types (TypeNames) for input parameters only. This is what
* is needed to look up an existing function, which is what is wanted by
* the productions that use this call.
*/
static List *
extractArgTypes(ObjectType objtype, List *parameters)
extractArgTypes(List *parameters)
{
List *result = NIL;
ListCell *i;
@ -16714,7 +16685,7 @@ extractArgTypes(ObjectType objtype, List *parameters)
{
FunctionParameter *p = (FunctionParameter *) lfirst(i);
if ((p->mode != FUNC_PARAM_OUT || objtype == OBJECT_PROCEDURE) && p->mode != FUNC_PARAM_TABLE)
if (p->mode != FUNC_PARAM_OUT && p->mode != FUNC_PARAM_TABLE)
result = lappend(result, p->argType);
}
return result;
@ -16727,7 +16698,7 @@ static List *
extractAggrArgTypes(List *aggrargs)
{
Assert(list_length(aggrargs) == 2);
return extractArgTypes(OBJECT_AGGREGATE, (List *) linitial(aggrargs));
return extractArgTypes((List *) linitial(aggrargs));
}
/* makeOrderedSetArgs()
@ -17023,7 +16994,9 @@ mergeTableFuncParameters(List *func_args, List *columns)
{
FunctionParameter *p = (FunctionParameter *) lfirst(lc);
if (p->mode != FUNC_PARAM_IN && p->mode != FUNC_PARAM_VARIADIC)
if (p->mode != FUNC_PARAM_DEFAULT &&
p->mode != FUNC_PARAM_IN &&
p->mode != FUNC_PARAM_VARIADIC)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("OUT and INOUT arguments aren't allowed in TABLE functions")));

View File

@ -48,9 +48,10 @@ static void unify_hypothetical_args(ParseState *pstate,
static Oid FuncNameAsType(List *funcname);
static Node *ParseComplexProjection(ParseState *pstate, const char *funcname,
Node *first_arg, int location);
static Oid LookupFuncNameInternal(List *funcname, int nargs,
const Oid *argtypes,
bool missing_ok, FuncLookupError *lookupError);
static Oid LookupFuncNameInternal(ObjectType objtype, List *funcname,
int nargs, const Oid *argtypes,
bool include_out_arguments, bool missing_ok,
FuncLookupError *lookupError);
/*
@ -82,7 +83,8 @@ static Oid LookupFuncNameInternal(List *funcname, int nargs,
* contain any SRF calls, last_srf can just be pstate->p_last_srf.
*
* proc_call is true if we are considering a CALL statement, so that the
* name must resolve to a procedure name, not anything else.
* name must resolve to a procedure name, not anything else. This flag
* also specifies that the argument list includes any OUT-mode arguments.
*/
Node *
ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
@ -263,7 +265,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
fdresult = func_get_detail(funcname, fargs, argnames, nargs,
actual_arg_types,
!func_variadic, true,
!func_variadic, true, proc_call,
&funcid, &rettype, &retset,
&nvargs, &vatype,
&declared_arg_types, &argdefaults);
@ -1395,6 +1397,7 @@ func_get_detail(List *funcname,
Oid *argtypes,
bool expand_variadic,
bool expand_defaults,
bool include_out_arguments,
Oid *funcid, /* return value */
Oid *rettype, /* return value */
bool *retset, /* return value */
@ -1419,7 +1422,7 @@ func_get_detail(List *funcname,
/* Get list of possible candidates from namespace search */
raw_candidates = FuncnameGetCandidates(funcname, nargs, fargnames,
expand_variadic, expand_defaults,
false);
include_out_arguments, false);
/*
* Quickly check if there is an exact match to the input datatypes (there
@ -1667,7 +1670,7 @@ func_get_detail(List *funcname,
defargnumbers = bms_add_member(defargnumbers,
firstdefarg[i]);
newdefaults = NIL;
i = pform->pronargs - pform->pronargdefaults;
i = best_candidate->nominalnargs - pform->pronargdefaults;
foreach(lc, defaults)
{
if (bms_is_member(i, defargnumbers))
@ -2041,12 +2044,15 @@ func_signature_string(List *funcname, int nargs,
*
* Possible errors:
* FUNCLOOKUP_NOSUCHFUNC: we can't find a function of this name.
* FUNCLOOKUP_AMBIGUOUS: nargs == -1 and more than one function matches.
* FUNCLOOKUP_AMBIGUOUS: more than one function matches.
*/
static Oid
LookupFuncNameInternal(List *funcname, int nargs, const Oid *argtypes,
bool missing_ok, FuncLookupError *lookupError)
LookupFuncNameInternal(ObjectType objtype, List *funcname,
int nargs, const Oid *argtypes,
bool include_out_arguments, bool missing_ok,
FuncLookupError *lookupError)
{
Oid result = InvalidOid;
FuncCandidateList clist;
/* NULL argtypes allowed for nullary functions only */
@ -2055,43 +2061,62 @@ LookupFuncNameInternal(List *funcname, int nargs, const Oid *argtypes,
/* Always set *lookupError, to forestall uninitialized-variable warnings */
*lookupError = FUNCLOOKUP_NOSUCHFUNC;
/* Get list of candidate objects */
clist = FuncnameGetCandidates(funcname, nargs, NIL, false, false,
missing_ok);
include_out_arguments, missing_ok);
/*
* If no arguments were specified, the name must yield a unique candidate.
*/
if (nargs < 0)
/* Scan list for a match to the arg types (if specified) and the objtype */
for (; clist != NULL; clist = clist->next)
{
if (clist)
/* Check arg type match, if specified */
if (nargs >= 0)
{
/* If there is a second match then it's ambiguous */
if (clist->next)
{
*lookupError = FUNCLOOKUP_AMBIGUOUS;
return InvalidOid;
}
/* Otherwise return the match */
return clist->oid;
/* if nargs==0, argtypes can be null; don't pass that to memcmp */
if (nargs > 0 &&
memcmp(argtypes, clist->args, nargs * sizeof(Oid)) != 0)
continue;
}
else
/* Check for duplicates reported by FuncnameGetCandidates */
if (!OidIsValid(clist->oid))
{
*lookupError = FUNCLOOKUP_AMBIGUOUS;
return InvalidOid;
}
/* Check objtype match, if specified */
switch (objtype)
{
case OBJECT_FUNCTION:
case OBJECT_AGGREGATE:
/* Ignore procedures */
if (get_func_prokind(clist->oid) == PROKIND_PROCEDURE)
continue;
break;
case OBJECT_PROCEDURE:
/* Ignore non-procedures */
if (get_func_prokind(clist->oid) != PROKIND_PROCEDURE)
continue;
break;
case OBJECT_ROUTINE:
/* no restriction */
break;
default:
Assert(false);
}
/* Check for multiple matches */
if (OidIsValid(result))
{
*lookupError = FUNCLOOKUP_AMBIGUOUS;
return InvalidOid;
}
/* OK, we have a candidate */
result = clist->oid;
}
/*
* Otherwise, look for a match to the arg types. FuncnameGetCandidates
* has ensured that there's at most one match in the returned list.
*/
while (clist)
{
/* if nargs==0, argtypes can be null; don't pass that to memcmp */
if (nargs == 0 ||
memcmp(argtypes, clist->args, nargs * sizeof(Oid)) == 0)
return clist->oid;
clist = clist->next;
}
return InvalidOid;
return result;
}
/*
@ -2111,6 +2136,10 @@ LookupFuncNameInternal(List *funcname, int nargs, const Oid *argtypes,
* If nargs == -1 and multiple functions are found matching this function name
* we will raise an ambiguous-function error, regardless of what missing_ok is
* set to.
*
* Only functions will be found; procedures will be ignored even if they
* match the name and argument types. (However, we don't trouble to reject
* aggregates or window functions here.)
*/
Oid
LookupFuncName(List *funcname, int nargs, const Oid *argtypes, bool missing_ok)
@ -2118,7 +2147,9 @@ LookupFuncName(List *funcname, int nargs, const Oid *argtypes, bool missing_ok)
Oid funcoid;
FuncLookupError lookupError;
funcoid = LookupFuncNameInternal(funcname, nargs, argtypes, missing_ok,
funcoid = LookupFuncNameInternal(OBJECT_FUNCTION,
funcname, nargs, argtypes,
false, missing_ok,
&lookupError);
if (OidIsValid(funcoid))
@ -2207,10 +2238,14 @@ LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func, bool missing_ok)
FUNC_MAX_ARGS)));
}
/*
* First, perform a lookup considering only input arguments (traditional
* Postgres rules).
*/
i = 0;
foreach(args_item, func->objargs)
{
TypeName *t = (TypeName *) lfirst(args_item);
TypeName *t = lfirst_node(TypeName, args_item);
argoids[i] = LookupTypeNameOid(NULL, t, missing_ok);
if (!OidIsValid(argoids[i]))
@ -2224,9 +2259,83 @@ LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func, bool missing_ok)
*/
nargs = func->args_unspecified ? -1 : argcount;
oid = LookupFuncNameInternal(func->objname, nargs, argoids, missing_ok,
/*
* In args_unspecified mode, also tell LookupFuncNameInternal to consider
* the object type, since there seems no reason not to. However, if we
* have an argument list, disable the objtype check, because we'd rather
* complain about "object is of wrong type" than "object doesn't exist".
* (Note that with args, FuncnameGetCandidates will have ensured there's
* only one argtype match, so we're not risking an ambiguity failure via
* this choice.)
*/
oid = LookupFuncNameInternal(func->args_unspecified ? objtype : OBJECT_ROUTINE,
func->objname, nargs, argoids,
false, missing_ok,
&lookupError);
/*
* If PROCEDURE or ROUTINE was specified, and we have an argument list
* that contains no parameter mode markers, and we didn't already discover
* that there's ambiguity, perform a lookup considering all arguments.
* (Note: for a zero-argument procedure, or in args_unspecified mode, the
* normal lookup is sufficient; so it's OK to require non-NIL objfuncargs
* to perform this lookup.)
*/
if ((objtype == OBJECT_PROCEDURE || objtype == OBJECT_ROUTINE) &&
func->objfuncargs != NIL &&
lookupError != FUNCLOOKUP_AMBIGUOUS)
{
bool have_param_mode = false;
/*
* Check for non-default parameter mode markers. If there are any,
* then the command does not conform to SQL-spec syntax, so we may
* assume that the traditional Postgres lookup method of considering
* only input parameters is sufficient. (Note that because the spec
* doesn't have OUT arguments for functions, we also don't need this
* hack in FUNCTION or AGGREGATE mode.)
*/
foreach(args_item, func->objfuncargs)
{
FunctionParameter *fp = lfirst_node(FunctionParameter, args_item);
if (fp->mode != FUNC_PARAM_DEFAULT)
{
have_param_mode = true;
break;
}
}
if (!have_param_mode)
{
Oid poid;
/* Without mode marks, objargs surely includes all params */
Assert(list_length(func->objfuncargs) == argcount);
/* For objtype == OBJECT_PROCEDURE, we can ignore non-procedures */
poid = LookupFuncNameInternal(objtype, func->objname,
argcount, argoids,
true, missing_ok,
&lookupError);
/* Combine results, handling ambiguity */
if (OidIsValid(poid))
{
if (OidIsValid(oid) && oid != poid)
{
/* oops, we got hits both ways, on different objects */
oid = InvalidOid;
lookupError = FUNCLOOKUP_AMBIGUOUS;
}
else
oid = poid;
}
else if (lookupError == FUNCLOOKUP_AMBIGUOUS)
oid = InvalidOid;
}
}
if (OidIsValid(oid))
{
/*
@ -2235,6 +2344,10 @@ LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func, bool missing_ok)
* we allow the objtype of FUNCTION to include aggregates and window
* functions; but we draw the line if the object is a procedure. That
* is a new enough feature that this historical rule does not apply.
*
* (This check is partially redundant with the objtype check in
* LookupFuncNameInternal; but not entirely, since we often don't tell
* LookupFuncNameInternal to apply that check at all.)
*/
switch (objtype)
{
@ -2345,28 +2458,32 @@ LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func, bool missing_ok)
(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
errmsg("function name \"%s\" is not unique",
NameListToString(func->objname)),
errhint("Specify the argument list to select the function unambiguously.")));
func->args_unspecified ?
errhint("Specify the argument list to select the function unambiguously.") : 0));
break;
case OBJECT_PROCEDURE:
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
errmsg("procedure name \"%s\" is not unique",
NameListToString(func->objname)),
errhint("Specify the argument list to select the procedure unambiguously.")));
func->args_unspecified ?
errhint("Specify the argument list to select the procedure unambiguously.") : 0));
break;
case OBJECT_AGGREGATE:
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
errmsg("aggregate name \"%s\" is not unique",
NameListToString(func->objname)),
errhint("Specify the argument list to select the aggregate unambiguously.")));
func->args_unspecified ?
errhint("Specify the argument list to select the aggregate unambiguously.") : 0));
break;
case OBJECT_ROUTINE:
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
errmsg("routine name \"%s\" is not unique",
NameListToString(func->objname)),
errhint("Specify the argument list to select the routine unambiguously.")));
func->args_unspecified ?
errhint("Specify the argument list to select the routine unambiguously.") : 0));
break;
default:

View File

@ -150,8 +150,8 @@ LookupOperWithArgs(ObjectWithArgs *oper, bool noError)
rightoid;
Assert(list_length(oper->objargs) == 2);
oprleft = linitial(oper->objargs);
oprright = lsecond(oper->objargs);
oprleft = linitial_node(TypeName, oper->objargs);
oprright = lsecond_node(TypeName, oper->objargs);
if (oprleft == NULL)
leftoid = InvalidOid;

View File

@ -93,7 +93,7 @@ regprocin(PG_FUNCTION_ARGS)
* pg_proc entries in the current search path.
*/
names = stringToQualifiedNameList(pro_name_or_oid);
clist = FuncnameGetCandidates(names, -1, NIL, false, false, false);
clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, false);
if (clist == NULL)
ereport(ERROR,
@ -127,7 +127,7 @@ to_regproc(PG_FUNCTION_ARGS)
* entries in the current search path.
*/
names = stringToQualifiedNameList(pro_name);
clist = FuncnameGetCandidates(names, -1, NIL, false, false, true);
clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true);
if (clist == NULL || clist->next != NULL)
PG_RETURN_NULL();
@ -175,7 +175,7 @@ regprocout(PG_FUNCTION_ARGS)
* qualify it.
*/
clist = FuncnameGetCandidates(list_make1(makeString(proname)),
-1, NIL, false, false, false);
-1, NIL, false, false, false, false);
if (clist != NULL && clist->next == NULL &&
clist->oid == proid)
nspname = NULL;
@ -262,7 +262,8 @@ regprocedurein(PG_FUNCTION_ARGS)
*/
parseNameAndArgTypes(pro_name_or_oid, false, &names, &nargs, argtypes);
clist = FuncnameGetCandidates(names, nargs, NIL, false, false, false);
clist = FuncnameGetCandidates(names, nargs, NIL, false, false,
false, false);
for (; clist; clist = clist->next)
{
@ -301,7 +302,7 @@ to_regprocedure(PG_FUNCTION_ARGS)
*/
parseNameAndArgTypes(pro_name, false, &names, &nargs, argtypes);
clist = FuncnameGetCandidates(names, nargs, NIL, false, false, true);
clist = FuncnameGetCandidates(names, nargs, NIL, false, false, false, true);
for (; clist; clist = clist->next)
{

View File

@ -3217,7 +3217,15 @@ print_function_arguments(StringInfo buf, HeapTuple proctup,
switch (argmode)
{
case PROARGMODE_IN:
modename = "";
/*
* For procedures, explicitly mark all argument modes, so as
* to avoid ambiguity with the SQL syntax for DROP PROCEDURE.
*/
if (proc->prokind == PROKIND_PROCEDURE)
modename = "IN ";
else
modename = "";
isinput = true;
break;
case PROARGMODE_INOUT:
@ -11615,7 +11623,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
if (!force_qualify)
p_result = func_get_detail(list_make1(makeString(proname)),
NIL, argnames, nargs, argtypes,
!use_variadic, true,
!use_variadic, true, false,
&p_funcid, &p_rettype,
&p_retset, &p_nvargs, &p_vatype,
&p_true_typeids, NULL);

View File

@ -1409,8 +1409,7 @@ get_func_trftypes(HeapTuple procTup,
* are set to NULL. You don't get anything if proargnames is NULL.
*/
int
get_func_input_arg_names(char prokind,
Datum proargnames, Datum proargmodes,
get_func_input_arg_names(Datum proargnames, Datum proargmodes,
char ***arg_names)
{
ArrayType *arr;
@ -1469,7 +1468,6 @@ get_func_input_arg_names(char prokind,
if (argmodes == NULL ||
argmodes[i] == PROARGMODE_IN ||
argmodes[i] == PROARGMODE_INOUT ||
(argmodes[i] == PROARGMODE_OUT && prokind == PROKIND_PROCEDURE) ||
argmodes[i] == PROARGMODE_VARIADIC)
{
char *pname = TextDatumGetCString(argnames[i]);