Re: CALL versus procedures with output-only arguments
От | Tom Lane |
---|---|
Тема | Re: CALL versus procedures with output-only arguments |
Дата | |
Msg-id | 1157704.1621889070@sss.pgh.pa.us обсуждение исходный текст |
Ответ на | Re: CALL versus procedures with output-only arguments (Tom Lane <tgl@sss.pgh.pa.us>) |
Список | pgsql-hackers |
I wrote: >> I think we ought to fix this so that OUT-only arguments are ignored >> when calling from SQL not plpgsql. Here's a draft patch for that. The docs probably need some more fiddling, but I think the code is in good shape. (I'm unsure about the JDBC compatibility issue, and would appreciate someone else testing that.) > I'm working on a patch to make it act that way. I've got some issues > yet to fix with named arguments (which seem rather undertested BTW, > since the patch is passing check-world even though I know it will > crash instantly on cases with CALL+named-args+out-only-args). After I'd finished fixing that, I realized that HEAD is really pretty broken for the case. For example regression=# CREATE PROCEDURE test_proc10(IN a int, OUT b int, IN c int) regression-# LANGUAGE plpgsql regression-# AS $$ regression$# BEGIN regression$# RAISE NOTICE 'a: %, b: %, c: %', a, b, c; regression$# b := a - c; regression$# END; regression$# $$; CREATE PROCEDURE regression=# DO $$ regression$# DECLARE _a int; _b int; _c int; regression$# BEGIN regression$# _a := 10; _b := 30; _c := 7; regression$# CALL test_proc10(a => _a, b => _b, c => _c); regression$# RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; regression$# END$$; ERROR: procedure test_proc10(a => integer, b => integer, c => integer) does not exist LINE 1: CALL test_proc10(a => _a, b => _b, c => _c) ^ HINT: No procedure matches the given name and argument types. You might need to add explicit type casts. QUERY: CALL test_proc10(a => _a, b => _b, c => _c) CONTEXT: PL/pgSQL function inline_code_block line 5 at CALL So even if you object to what I'm trying to do here, there is work to be done. regards, tom lane diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 8aebc4d12f..6da45e049b 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -5901,9 +5901,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l <para> An array of the data types of the function arguments. This includes only input arguments (including <literal>INOUT</literal> and - <literal>VARIADIC</literal> arguments), as well as - <literal>OUT</literal> parameters of procedures, and thus represents - the call signature of the function or procedure. + <literal>VARIADIC</literal> arguments), and thus represents + the call signature of the function. </para></entry> </row> diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 52f60c827c..46732b0012 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -480,7 +480,7 @@ $$ LANGUAGE plpgsql; <para> To call a function with <literal>OUT</literal> parameters, omit the - output parameter in the function call: + output parameter(s) in the function call: <programlisting> SELECT sales_tax(100.00); </programlisting> @@ -520,18 +520,20 @@ BEGIN prod := x * y; END; $$ LANGUAGE plpgsql; -</programlisting> - In a call to a procedure, all the parameters must be specified. For - output parameters, <literal>NULL</literal> may be specified. -<programlisting> -CALL sum_n_product(2, 4, NULL, NULL); +CALL sum_n_product(2, 4); sum | prod -----+------ 6 | 8 </programlisting> - Output parameters in procedures become more interesting in nested calls, - where they can be assigned to variables. See <xref + </para> + + <para> + However, calling a procedure with output parameters from within + <application>PL/pgSQL</application> works differently: the parameter + list must include a variable matching each <literal>OUT</literal> + or <literal>INOUT</literal> parameter, and that variable receives + the procedure's result. See <xref linkend="plpgsql-statements-calling-procedure"/> for details. </para> diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml index 38fd60128b..c819c7bb4e 100644 --- a/doc/src/sgml/ref/alter_extension.sgml +++ b/doc/src/sgml/ref/alter_extension.sgml @@ -212,12 +212,11 @@ ALTER EXTENSION <replaceable class="parameter">name</replaceable> DROP <replacea argument: <literal>IN</literal>, <literal>OUT</literal>, <literal>INOUT</literal>, or <literal>VARIADIC</literal>. If omitted, the default is <literal>IN</literal>. - Note that <command>ALTER EXTENSION</command> does not actually pay any - attention to <literal>OUT</literal> arguments for functions and - aggregates (but not procedures), since only the input arguments are - needed to determine the function's identity. So it is sufficient to - list the <literal>IN</literal>, <literal>INOUT</literal>, and - <literal>VARIADIC</literal> arguments for functions and aggregates. + Note that <command>ALTER EXTENSION</command> does not actually pay + any attention to <literal>OUT</literal> arguments, since only the input + arguments are needed to determine the function's identity. + So it is sufficient to list the <literal>IN</literal>, <literal>INOUT</literal>, + and <literal>VARIADIC</literal> arguments. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index eda91b4e24..6e8ced3eaf 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -178,12 +178,11 @@ COMMENT ON argument: <literal>IN</literal>, <literal>OUT</literal>, <literal>INOUT</literal>, or <literal>VARIADIC</literal>. If omitted, the default is <literal>IN</literal>. - Note that <command>COMMENT</command> does not actually pay any attention - to <literal>OUT</literal> arguments for functions and aggregates (but - not procedures), since only the input arguments are needed to determine - the function's identity. So it is sufficient to list the - <literal>IN</literal>, <literal>INOUT</literal>, and - <literal>VARIADIC</literal> arguments for functions and aggregates. + Note that <command>COMMENT</command> does not actually pay + any attention to <literal>OUT</literal> arguments, since only the input + arguments are needed to determine the function's identity. + So it is sufficient to list the <literal>IN</literal>, <literal>INOUT</literal>, + and <literal>VARIADIC</literal> arguments. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml index 9b87bcd519..e9688cce21 100644 --- a/doc/src/sgml/ref/security_label.sgml +++ b/doc/src/sgml/ref/security_label.sgml @@ -127,12 +127,11 @@ SECURITY LABEL [ FOR <replaceable class="parameter">provider</replaceable> ] ON argument: <literal>IN</literal>, <literal>OUT</literal>, <literal>INOUT</literal>, or <literal>VARIADIC</literal>. If omitted, the default is <literal>IN</literal>. - Note that <command>SECURITY LABEL</command> does not actually pay any - attention to <literal>OUT</literal> arguments for functions and - aggregates (but not procedures), since only the input arguments are - needed to determine the function's identity. So it is sufficient to - list the <literal>IN</literal>, <literal>INOUT</literal>, and - <literal>VARIADIC</literal> arguments for functions and aggregates. + Note that <command>SECURITY LABEL</command> does not actually + pay any attention to <literal>OUT</literal> arguments, since only the input + arguments are needed to determine the function's identity. + So it is sufficient to list the <literal>IN</literal>, <literal>INOUT</literal>, + and <literal>VARIADIC</literal> arguments. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 41bcc5b79d..a0a1c99910 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -769,47 +769,6 @@ DROP FUNCTION sum_n_product (int, int); </para> </sect2> - <sect2 id="xfunc-output-parameters-proc"> - <title><acronym>SQL</acronym> Procedures with Output Parameters</title> - - <indexterm> - <primary>procedures</primary> - <secondary>output parameter</secondary> - </indexterm> - - <para> - Output parameters are also supported in procedures, but they work a bit - differently from functions. Notably, output parameters - <emphasis>are</emphasis> included in the signature of a procedure and - must be specified in the procedure call. - </para> - - <para> - For example, the bank account debiting routine from earlier could be - written like this: -<programlisting> -CREATE PROCEDURE tp1 (accountno integer, debit numeric, OUT new_balance numeric) AS $$ - UPDATE bank - SET balance = balance - debit - WHERE accountno = tp1.accountno - RETURNING balance; -$$ LANGUAGE SQL; -</programlisting> - To call this procedure, it is irrelevant what is passed as the argument - of the <literal>OUT</literal> parameter, so you could pass - <literal>NULL</literal>: -<programlisting> -CALL tp1(17, 100.0, NULL); -</programlisting> - </para> - - <para> - Procedures with output parameters are more useful in PL/pgSQL, where the - output parameters can be assigned to variables. See <xref - linkend="plpgsql-statements-calling-procedure"/> for details. - </para> - </sect2> - <sect2 id="xfunc-sql-variadic-functions"> <title><acronym>SQL</acronym> Functions with Variable Numbers of Arguments</title> diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 005e029c38..fd767fc5cf 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -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; diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 5197076c76..1f63d8081b 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -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); diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 5403110820..1454d2fb67 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -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++) diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 4c12aa33df..894c6a8f54 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -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)); @@ -290,29 +290,35 @@ interpret_function_parameter_list(ParseState *pstate, /* handle input parameters */ if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE) { - 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 */ + /* 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 signature parameter"))); - sigArgTypes[sigArgCount++] = toid; + 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 output parameters */ if (fp->mode != FUNC_PARAM_IN && fp->mode != 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 that is including OUT + * parameters. + */ + 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++; @@ -432,13 +438,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 that is including OUT parameters. + */ + 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 +2195,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 +2237,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 +2267,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++; } diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 39580f7d57..ec934e6cf7 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -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 */ @@ -384,6 +383,7 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var) pstate->p_last_srf, NULL, false, + false, cref->location); } diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index b8bd05e894..d33fd2a995 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -2812,6 +2812,7 @@ _SPI_error_callback(void *arg) case RAW_PARSE_PLPGSQL_ASSIGN3: errcontext("PL/pgSQL assignment \"%s\"", query); break; + case RAW_PARSE_PLPGSQL_CALL: default: errcontext("SQL statement \"%s\"", query); break; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 90770a89b0..e473b59445 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3478,6 +3478,8 @@ _copyCallStmt(const CallStmt *from) COPY_NODE_FIELD(funccall); COPY_NODE_FIELD(funcexpr); + COPY_NODE_FIELD(outargs); + COPY_SCALAR_FIELD(hasoutargs); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index ce76d093dd..1f9b029316 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1241,6 +1241,8 @@ _equalCallStmt(const CallStmt *a, const CallStmt *b) { COMPARE_NODE_FIELD(funccall); COMPARE_NODE_FIELD(funcexpr); + COMPARE_NODE_FIELD(outargs); + COMPARE_SCALAR_FIELD(hasoutargs); return true; } diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index e117ab976e..84a800479b 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -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, diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 201b88d1ad..9e198570af 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -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,15 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt) List *targs; ListCell *lc; Node *node; + FuncExpr *fexpr; + HeapTuple proctup; + 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) { @@ -2958,12 +2965,97 @@ transformCallStmt(ParseState *pstate, CallStmt *stmt) pstate->p_last_srf, stmt->funccall, true, + stmt->hasoutargs, stmt->funccall->location); 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, + stmt->hasoutargs, + fexpr->funcresulttype, + proctup); + + /* + * If there are (might be) OUT arguments in the argument list, split the + * list into input arguments in fexpr->args and output arguments in + * stmt->outargs. INOUT arguments appear in both lists. + */ + if (stmt->hasoutargs) + { + Datum proargmodes; + bool isNull; + + /* Fetch proargmodes; if it's null, there are no OUT args */ + proargmodes = SysCacheGetAttr(PROCOID, proctup, + Anum_pg_proc_proargmodes, + &isNull); + if (!isNull) + { + 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; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index aaf1a51f68..be4f712f07 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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 @@ -753,6 +753,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %token MODE_PLPGSQL_ASSIGN1 %token MODE_PLPGSQL_ASSIGN2 %token MODE_PLPGSQL_ASSIGN3 +%token MODE_PLPGSQL_CALL /* Precedence: lowest to highest */ @@ -858,6 +859,13 @@ parse_toplevel: pg_yyget_extra(yyscanner)->parsetree = list_make1(makeRawStmt((Node *) n, 0)); } + | MODE_PLPGSQL_CALL CallStmt + { + CallStmt *n = (CallStmt *) $2; + n->hasoutargs = true; + pg_yyget_extra(yyscanner)->parsetree = + list_make1(makeRawStmt((Node *) n, 0)); + } ; /* @@ -1038,6 +1046,7 @@ CallStmt: CALL func_application { CallStmt *n = makeNode(CallStmt); n->funccall = castNode(FuncCall, $2); + n->hasoutargs = false; /* assume it's plain SQL */ $$ = (Node *)n; } ; @@ -4755,7 +4764,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; @@ -4764,7 +4773,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; @@ -6503,7 +6512,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; @@ -6511,7 +6520,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; @@ -6657,7 +6666,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); @@ -7021,7 +7030,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; @@ -7029,7 +7038,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; @@ -7554,33 +7563,20 @@ 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; } - | 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))); @@ -7604,24 +7600,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. @@ -8050,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; @@ -8058,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; @@ -8114,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; @@ -8124,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; @@ -8134,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; @@ -8144,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; @@ -8616,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; @@ -8634,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; @@ -9045,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; @@ -9054,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; @@ -9185,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; @@ -9194,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; @@ -9496,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; @@ -9504,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; @@ -16692,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; @@ -16708,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; @@ -16721,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() diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f928c32311..863904557d 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -417,6 +417,7 @@ transformIndirection(ParseState *pstate, A_Indirection *ind) last_srf, NULL, false, + false, location); if (newresult == NULL) unknown_attribute(pstate, result, strVal(n), location); @@ -644,6 +645,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) pstate->p_last_srf, NULL, false, + false, cref->location); } break; @@ -694,6 +696,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) pstate->p_last_srf, NULL, false, + false, cref->location); } break; @@ -757,6 +760,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) pstate->p_last_srf, NULL, false, + false, cref->location); } break; @@ -1388,6 +1392,7 @@ transformFuncCall(ParseState *pstate, FuncCall *fn) last_srf, fn, false, + false, fn->location); } diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index baac089d68..4b0ae519a7 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -83,10 +83,15 @@ static Oid LookupFuncNameInternal(List *funcname, int nargs, * * proc_call is true if we are considering a CALL statement, so that the * name must resolve to a procedure name, not anything else. + * + * include_out_arguments is true if the CALL argument list should include + * OUT-mode arguments. */ Node * ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, - Node *last_srf, FuncCall *fn, bool proc_call, int location) + Node *last_srf, FuncCall *fn, + bool proc_call, bool include_out_arguments, + int location) { bool is_column = (fn == NULL); List *agg_order = (fn ? fn->agg_order : NIL); @@ -264,7 +269,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, include_out_arguments, &funcid, &rettype, &retset, &nvargs, &vatype, &declared_arg_types, &argdefaults); @@ -1396,6 +1401,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 */ @@ -1420,7 +1426,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 @@ -1668,7 +1674,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)) @@ -2057,7 +2063,7 @@ LookupFuncNameInternal(List *funcname, int nargs, const Oid *argtypes, *lookupError = FUNCLOOKUP_NOSUCHFUNC; clist = FuncnameGetCandidates(funcname, nargs, NIL, false, false, - missing_ok); + false, missing_ok); /* * If no arguments were specified, the name must yield a unique candidate. diff --git a/src/backend/parser/parse_param.c b/src/backend/parser/parse_param.c index 68a5534393..96b9e15c2d 100644 --- a/src/backend/parser/parse_param.c +++ b/src/backend/parser/parse_param.c @@ -163,15 +163,6 @@ variable_paramref_hook(ParseState *pstate, ParamRef *pref) if (*pptype == InvalidOid) *pptype = UNKNOWNOID; - /* - * If the argument is of type void and it's procedure call, interpret it - * as unknown. This allows the JDBC driver to not have to distinguish - * function and procedure calls. See also another component of this hack - * in ParseFuncOrColumn(). - */ - if (*pptype == VOIDOID && pstate->p_expr_kind == EXPR_KIND_CALL_ARGUMENT) - *pptype = UNKNOWNOID; - param = makeNode(Param); param->paramkind = PARAM_EXTERN; param->paramid = paramno; diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index 875de7ba28..d87cadb42b 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -61,7 +61,8 @@ raw_parser(const char *str, RawParseMode mode) MODE_PLPGSQL_EXPR, /* RAW_PARSE_PLPGSQL_EXPR */ MODE_PLPGSQL_ASSIGN1, /* RAW_PARSE_PLPGSQL_ASSIGN1 */ MODE_PLPGSQL_ASSIGN2, /* RAW_PARSE_PLPGSQL_ASSIGN2 */ - MODE_PLPGSQL_ASSIGN3 /* RAW_PARSE_PLPGSQL_ASSIGN3 */ + MODE_PLPGSQL_ASSIGN3, /* RAW_PARSE_PLPGSQL_ASSIGN3 */ + MODE_PLPGSQL_CALL /* RAW_PARSE_PLPGSQL_CALL */ }; yyextra.have_lookahead = true; diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c index f998fe2076..e4fb9d31d9 100644 --- a/src/backend/utils/adt/regproc.c +++ b/src/backend/utils/adt/regproc.c @@ -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) { diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 84ad62caea..fb3d7ab8ef 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -11615,7 +11615,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); diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 717b62907c..e94b8037ec 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -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]); diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index aa2774e2d4..b98f284356 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -30,6 +30,7 @@ typedef struct _FuncCandidateList struct _FuncCandidateList *next; int pathpos; /* for internal use of namespace lookup */ Oid oid; /* the function or operator's OID */ + int nominalnargs; /* either pronargs or length(proallargtypes) */ int nargs; /* number of arg types returned */ int nvargs; /* number of args to become variadic array */ int ndargs; /* number of defaulted args */ @@ -99,6 +100,7 @@ extern FuncCandidateList FuncnameGetCandidates(List *names, int nargs, List *argnames, bool expand_variadic, bool expand_defaults, + bool include_out_arguments, bool missing_ok); extern bool FunctionIsVisible(Oid funcid); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 448d9898cb..a65afe7bc4 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -91,7 +91,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce * proargtypes */ - /* parameter types (excludes OUT params of functions) */ + /* parameter types (excludes OUT params) */ oidvector proargtypes BKI_LOOKUP(pg_type) BKI_FORCE_NOT_NULL; #ifdef CATALOG_VARLEN diff --git a/src/include/funcapi.h b/src/include/funcapi.h index 83e6bc2a1f..f1304d47e3 100644 --- a/src/include/funcapi.h +++ b/src/include/funcapi.h @@ -172,8 +172,7 @@ extern int get_func_arg_info(HeapTuple procTup, Oid **p_argtypes, char ***p_argnames, char **p_argmodes); -extern int get_func_input_arg_names(char prokind, - Datum proargnames, Datum proargmodes, +extern int get_func_input_arg_names(Datum proargnames, Datum proargmodes, char ***arg_names); extern int get_func_trftypes(HeapTuple procTup, Oid **p_trftypes); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ef73342019..ef236e40f0 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2998,13 +2998,22 @@ typedef struct InlineCodeBlock /* ---------------------- * CALL statement + * + * In plain SQL, the argument list appearing in funccall does not include + * entries for OUT-mode arguments; but in PL/pgSQL it does. We remove any + * such arguments from the transformed funcexpr. The outargs list contains + * copies of the expressions for all output arguments, in the order of the + * procedure's declared arguments. (outargs is never evaluated, but is + * useful to the caller as a reference for what to assign to.) * ---------------------- */ typedef struct CallStmt { NodeTag type; FuncCall *funccall; /* from the parser */ - FuncExpr *funcexpr; /* transformed */ + FuncExpr *funcexpr; /* transformed call, with only input args */ + List *outargs; /* transformed output-argument expressions */ + bool hasoutargs; /* does funccall include OUT args? */ } CallStmt; typedef struct CallContext diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index 68ebb84bf5..41b49b2662 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -153,7 +153,8 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node); extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod, Oid result_collation); -extern List *expand_function_arguments(List *args, Oid result_type, +extern List *expand_function_arguments(List *args, bool include_out_arguments, + Oid result_type, struct HeapTupleData *func_tuple); /* in util/predtest.c: */ diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h index aaf07f8f73..682d158940 100644 --- a/src/include/parser/parse_func.h +++ b/src/include/parser/parse_func.h @@ -32,13 +32,15 @@ typedef enum extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, - Node *last_srf, FuncCall *fn, bool proc_call, + Node *last_srf, FuncCall *fn, + bool proc_call, bool include_out_arguments, int location); extern FuncDetailCode func_get_detail(List *funcname, List *fargs, List *fargnames, int nargs, Oid *argtypes, bool expand_variadic, bool expand_defaults, + bool include_out_arguments, Oid *funcid, Oid *rettype, bool *retset, int *nvargs, Oid *vatype, Oid **true_typeids, List **argdefaults); diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h index 853b0f1606..860a560c0c 100644 --- a/src/include/parser/parser.h +++ b/src/include/parser/parser.h @@ -33,6 +33,9 @@ * RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement, * and return a one-element List containing a RawStmt node. "n" * gives the number of dotted names comprising the target ColumnRef. + * + * RAW_PARSE_PLPGSQL_CALL: parse a PL/pgSQL CALL statement, and return + * a one-element List containing a RawStmt node. */ typedef enum { @@ -41,7 +44,8 @@ typedef enum RAW_PARSE_PLPGSQL_EXPR, RAW_PARSE_PLPGSQL_ASSIGN1, RAW_PARSE_PLPGSQL_ASSIGN2, - RAW_PARSE_PLPGSQL_ASSIGN3 + RAW_PARSE_PLPGSQL_ASSIGN3, + RAW_PARSE_PLPGSQL_CALL } RawParseMode; /* Values for the backslash_quote GUC */ diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out index c804a3ffbf..02887399df 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_call.out +++ b/src/pl/plpgsql/src/expected/plpgsql_call.out @@ -304,6 +304,79 @@ END $$; NOTICE: a: 10, b: <NULL> NOTICE: _a: 10, _b: 20 +CREATE PROCEDURE test_proc10(IN a int, OUT b int, IN c int DEFAULT 11) +LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'a: %, b: %, c: %', a, b, c; + b := a - c; +END; +$$; +DO $$ +DECLARE _a int; _b int; _c int; +BEGIN + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, _b, _c); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, _b, c => _c); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(a => _a, b => _b, c => _c); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, c => _c, b => _b); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, _b); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, b => _b); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(b => _b, a => _a); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; +END +$$; +NOTICE: a: 10, b: <NULL>, c: 7 +NOTICE: _a: 10, _b: 3, _c: 7 +NOTICE: a: 10, b: <NULL>, c: 7 +NOTICE: _a: 10, _b: 3, _c: 7 +NOTICE: a: 10, b: <NULL>, c: 7 +NOTICE: _a: 10, _b: 3, _c: 7 +NOTICE: a: 10, b: <NULL>, c: 7 +NOTICE: _a: 10, _b: 3, _c: 7 +NOTICE: a: 10, b: <NULL>, c: 11 +NOTICE: _a: 10, _b: -1, _c: 7 +NOTICE: a: 10, b: <NULL>, c: 11 +NOTICE: _a: 10, _b: -1, _c: 7 +NOTICE: a: 10, b: <NULL>, c: 11 +NOTICE: _a: 10, _b: -1, _c: 7 +-- OUT + VARIADIC +CREATE PROCEDURE test_proc11(a OUT int, VARIADIC b int[]) +LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'a: %, b: %', a, b; + a := b[1] + b[2]; +END; +$$; +DO $$ +DECLARE _a int; _b int; _c int; +BEGIN + _a := 10; _b := 30; _c := 7; + CALL test_proc11(_a, _b, _c); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; +END +$$; +NOTICE: a: <NULL>, b: {30,7} +NOTICE: _a: 37, _b: 30, _c: 7 -- transition variable assignment TRUNCATE test1; CREATE FUNCTION triggerfunc1() RETURNS trigger diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index ce8d97447d..a68a2077c3 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -460,7 +460,6 @@ do_compile(FunctionCallInfo fcinfo, /* Remember arguments in appropriate arrays */ if (argmode == PROARGMODE_IN || argmode == PROARGMODE_INOUT || - (argmode == PROARGMODE_OUT && function->fn_prokind == PROKIND_PROCEDURE) || argmode == PROARGMODE_VARIADIC) in_arg_varnos[num_in_args++] = argvariable->dno; if (argmode == PROARGMODE_OUT || diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 78b593d12c..31b9e85744 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -2246,18 +2246,17 @@ make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) { List *plansources; CachedPlanSource *plansource; - Node *node; + CallStmt *stmt; FuncExpr *funcexpr; HeapTuple func_tuple; - List *funcargs; Oid *argtypes; char **argnames; char *argmodes; + int numargs; MemoryContext oldcontext; PLpgSQL_row *row; int nfields; int i; - ListCell *lc; /* Use eval_mcontext for any cruft accumulated here */ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); @@ -2271,11 +2270,12 @@ make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) plansource = (CachedPlanSource *) linitial(plansources); if (list_length(plansource->query_list) != 1) elog(ERROR, "query for CALL statement is not a CallStmt"); - node = linitial_node(Query, plansource->query_list)->utilityStmt; - if (node == NULL || !IsA(node, CallStmt)) + stmt = (CallStmt *) linitial_node(Query, + plansource->query_list)->utilityStmt; + if (stmt == NULL || !IsA(stmt, CallStmt)) elog(ERROR, "query for CALL statement is not a CallStmt"); - funcexpr = ((CallStmt *) node)->funcexpr; + funcexpr = stmt->funcexpr; func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcexpr->funcid)); @@ -2284,16 +2284,10 @@ make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) funcexpr->funcid); /* - * Extract function arguments, and expand any named-arg notation + * Get the argument names and modes, so that we can deliver on-point error + * messages when something is wrong. */ - funcargs = expand_function_arguments(funcexpr->args, - funcexpr->funcresulttype, - func_tuple); - - /* - * Get the argument names and modes, too - */ - get_func_arg_info(func_tuple, &argtypes, &argnames, &argmodes); + numargs = get_func_arg_info(func_tuple, &argtypes, &argnames, &argmodes); ReleaseSysCache(func_tuple); @@ -2307,7 +2301,7 @@ make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) row->dtype = PLPGSQL_DTYPE_ROW; row->refname = "(unnamed row)"; row->lineno = -1; - row->varnos = (int *) palloc(sizeof(int) * list_length(funcargs)); + row->varnos = (int *) palloc(numargs * sizeof(int)); MemoryContextSwitchTo(get_eval_mcontext(estate)); @@ -2317,15 +2311,14 @@ make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) * Datum. */ nfields = 0; - i = 0; - foreach(lc, funcargs) + for (i = 0; i < numargs; i++) { - Node *n = lfirst(lc); - if (argmodes && (argmodes[i] == PROARGMODE_INOUT || argmodes[i] == PROARGMODE_OUT)) { + Node *n = list_nth(stmt->outargs, nfields); + if (IsA(n, Param)) { Param *param = (Param *) n; @@ -2348,9 +2341,10 @@ make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) i + 1))); } } - i++; } + Assert(nfields == list_length(stmt->outargs)); + row->nfields = nfields; MemoryContextSwitchTo(oldcontext); diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 3fcca43b90..81541cacdb 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -944,7 +944,10 @@ stmt_call : K_CALL new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; plpgsql_push_back_token(K_CALL); - new->expr = read_sql_stmt(); + new->expr = read_sql_construct(';', 0, 0, ";", + RAW_PARSE_PLPGSQL_CALL, + false, true, true, + NULL, NULL); new->is_call = true; /* Remember we may need a procedure resource owner */ diff --git a/src/pl/plpgsql/src/sql/plpgsql_call.sql b/src/pl/plpgsql/src/sql/plpgsql_call.sql index c61a75be9b..755935a006 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_call.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql @@ -281,6 +281,68 @@ BEGIN END $$; +CREATE PROCEDURE test_proc10(IN a int, OUT b int, IN c int DEFAULT 11) +LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'a: %, b: %, c: %', a, b, c; + b := a - c; +END; +$$; + +DO $$ +DECLARE _a int; _b int; _c int; +BEGIN + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, _b, _c); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, _b, c => _c); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(a => _a, b => _b, c => _c); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, c => _c, b => _b); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, _b); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(_a, b => _b); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; + + _a := 10; _b := 30; _c := 7; + CALL test_proc10(b => _b, a => _a); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; +END +$$; + +-- OUT + VARIADIC + +CREATE PROCEDURE test_proc11(a OUT int, VARIADIC b int[]) +LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'a: %, b: %', a, b; + a := b[1] + b[2]; +END; +$$; + +DO $$ +DECLARE _a int; _b int; _c int; +BEGIN + _a := 10; _b := 30; _c := 7; + CALL test_proc11(_a, _b, _c); + RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c; +END +$$; + -- transition variable assignment diff --git a/src/pl/plpython/expected/plpython_call.out b/src/pl/plpython/expected/plpython_call.out index c3f3c8e95e..8151b5ec32 100644 --- a/src/pl/plpython/expected/plpython_call.out +++ b/src/pl/plpython/expected/plpython_call.out @@ -56,7 +56,6 @@ CALL test_proc6(2, 3, 4); CREATE PROCEDURE test_proc9(IN a int, OUT b int) LANGUAGE plpythonu AS $$ -plpy.notice("a: %s, b: %s" % (a, b)) return (a * 2,) $$; DO $$ @@ -67,7 +66,6 @@ BEGIN RAISE NOTICE '_a: %, _b: %', _a, _b; END $$; -NOTICE: a: 10, b: None NOTICE: _a: 10, _b: 20 DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c index b7c0b5cebe..494f109b32 100644 --- a/src/pl/plpython/plpy_procedure.c +++ b/src/pl/plpython/plpy_procedure.c @@ -272,7 +272,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) /* proc->nargs was initialized to 0 above */ for (i = 0; i < total; i++) { - if ((modes[i] != PROARGMODE_OUT || proc->is_procedure) && + if (modes[i] != PROARGMODE_OUT && modes[i] != PROARGMODE_TABLE) (proc->nargs)++; } @@ -288,7 +288,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) Form_pg_type argTypeStruct; if (modes && - ((modes[i] == PROARGMODE_OUT && !proc->is_procedure) || + (modes[i] == PROARGMODE_OUT || modes[i] == PROARGMODE_TABLE)) continue; /* skip OUT arguments */ diff --git a/src/pl/plpython/sql/plpython_call.sql b/src/pl/plpython/sql/plpython_call.sql index 46e89b1a9e..5d0b11c41c 100644 --- a/src/pl/plpython/sql/plpython_call.sql +++ b/src/pl/plpython/sql/plpython_call.sql @@ -59,7 +59,6 @@ CALL test_proc6(2, 3, 4); CREATE PROCEDURE test_proc9(IN a int, OUT b int) LANGUAGE plpythonu AS $$ -plpy.notice("a: %s, b: %s" % (a, b)) return (a * 2,) $$; diff --git a/src/pl/tcl/expected/pltcl_call.out b/src/pl/tcl/expected/pltcl_call.out index f0eb356cf2..e4498375ec 100644 --- a/src/pl/tcl/expected/pltcl_call.out +++ b/src/pl/tcl/expected/pltcl_call.out @@ -53,7 +53,7 @@ CALL test_proc6(2, 3, 4); CREATE PROCEDURE test_proc9(IN a int, OUT b int) LANGUAGE pltcl AS $$ -elog NOTICE "a: $1, b: $2" +elog NOTICE "a: $1" return [list b [expr {$1 * 2}]] $$; DO $$ @@ -64,7 +64,7 @@ BEGIN RAISE NOTICE '_a: %, _b: %', _a, _b; END $$; -NOTICE: a: 10, b: +NOTICE: a: 10 NOTICE: _a: 10, _b: 20 DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; diff --git a/src/pl/tcl/sql/pltcl_call.sql b/src/pl/tcl/sql/pltcl_call.sql index 963277e1fb..37efbdefc2 100644 --- a/src/pl/tcl/sql/pltcl_call.sql +++ b/src/pl/tcl/sql/pltcl_call.sql @@ -57,7 +57,7 @@ CALL test_proc6(2, 3, 4); CREATE PROCEDURE test_proc9(IN a int, OUT b int) LANGUAGE pltcl AS $$ -elog NOTICE "a: $1, b: $2" +elog NOTICE "a: $1" return [list b [expr {$1 * 2}]] $$; diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out index d45575561e..b658f1b9ef 100644 --- a/src/test/regress/expected/create_procedure.out +++ b/src/test/regress/expected/create_procedure.out @@ -217,12 +217,52 @@ AS $$ INSERT INTO cp_test VALUES (1, 'a'); SELECT 1; $$; -CALL ptest9(NULL); +CALL ptest9(); a --- 1 (1 row) +-- check named-parameter matching +CREATE PROCEDURE ptest10(OUT a int, IN b int, IN c int) +LANGUAGE SQL AS $$ SELECT b - c $$; +CALL ptest10(7, 4); + a +--- + 3 +(1 row) + +CALL ptest10(b => 8, c => 2); + a +--- + 6 +(1 row) + +CALL ptest10(7, c => 2); + a +--- + 5 +(1 row) + +CALL ptest10(c => 4, b => 11); + a +--- + 7 +(1 row) + +CALL ptest10(a => 0, b => 8, c => 2); -- error +ERROR: procedure ptest10(a => integer, b => integer, c => integer) does not exist +LINE 1: CALL ptest10(a => 0, b => 8, c => 2); + ^ +HINT: No procedure matches the given name and argument types. You might need to add explicit type casts. +CREATE PROCEDURE ptest11(a OUT int, VARIADIC b int[]) LANGUAGE SQL + AS $$ SELECT b[1] + b[2] $$; +CALL ptest11(11, 12, 13); + a +---- + 23 +(1 row) + -- various error cases CALL version(); -- error: not a procedure ERROR: version() is not a procedure @@ -242,6 +282,12 @@ CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES ( ERROR: invalid attribute in procedure definition LINE 1: CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT I... ^ +CREATE PROCEDURE ptestx(a VARIADIC int[], b OUT int) LANGUAGE SQL + AS $$ SELECT a[1] $$; +ERROR: VARIADIC parameter must be the last parameter +CREATE PROCEDURE ptestx(a int DEFAULT 42, b OUT int) LANGUAGE SQL + AS $$ SELECT a $$; +ERROR: procedure OUT parameters cannot appear after one with a default value ALTER PROCEDURE ptest1(text) STRICT; ERROR: invalid attribute in procedure definition LINE 1: ALTER PROCEDURE ptest1(text) STRICT; diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql index 76f781c0b9..81d0a97043 100644 --- a/src/test/regress/sql/create_procedure.sql +++ b/src/test/regress/sql/create_procedure.sql @@ -153,7 +153,22 @@ INSERT INTO cp_test VALUES (1, 'a'); SELECT 1; $$; -CALL ptest9(NULL); +CALL ptest9(); + +-- check named-parameter matching +CREATE PROCEDURE ptest10(OUT a int, IN b int, IN c int) +LANGUAGE SQL AS $$ SELECT b - c $$; + +CALL ptest10(7, 4); +CALL ptest10(b => 8, c => 2); +CALL ptest10(7, c => 2); +CALL ptest10(c => 4, b => 11); +CALL ptest10(a => 0, b => 8, c => 2); -- error + +CREATE PROCEDURE ptest11(a OUT int, VARIADIC b int[]) LANGUAGE SQL + AS $$ SELECT b[1] + b[2] $$; + +CALL ptest11(11, 12, 13); -- various error cases @@ -163,6 +178,10 @@ CALL sum(1); -- error: not a procedure CREATE PROCEDURE ptestx() LANGUAGE SQL WINDOW AS $$ INSERT INTO cp_test VALUES (1, 'a') $$; CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES (1, 'a') $$; +CREATE PROCEDURE ptestx(a VARIADIC int[], b OUT int) LANGUAGE SQL + AS $$ SELECT a[1] $$; +CREATE PROCEDURE ptestx(a int DEFAULT 42, b OUT int) LANGUAGE SQL + AS $$ SELECT a $$; ALTER PROCEDURE ptest1(text) STRICT; ALTER FUNCTION ptest1(text) VOLATILE; -- error: not a function
В списке pgsql-hackers по дате отправления:
Предыдущее
От: Andres FreundДата:
Сообщение: Re: Performance degradation of REFRESH MATERIALIZED VIEW