mirror of
https://github.com/postgres/postgres.git
synced 2025-06-17 17:02:08 +03:00
Support INOUT arguments in procedures
In a top-level CALL, the values of INOUT arguments will be returned as a result row. In PL/pgSQL, the values are assigned back to the input arguments. In other languages, the same convention as for return a record from a function is used. That does not require any code changes in the PL implementations. Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com>
This commit is contained in:
@ -278,6 +278,20 @@ SELECT * FROM perl_row();
|
|||||||
hash will be returned as null values.
|
hash will be returned as null values.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Similarly, output arguments of procedures can be returned as a hash
|
||||||
|
reference:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE PROCEDURE perl_triple(INOUT a integer, INOUT b integer) AS $$
|
||||||
|
my ($a, $b) = @_;
|
||||||
|
return {a => $a * 3, b => $b * 3};
|
||||||
|
$$ LANGUAGE plperl;
|
||||||
|
|
||||||
|
CALL perl_triple(5, 10);
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
PL/Perl functions can also return sets of either scalar or
|
PL/Perl functions can also return sets of either scalar or
|
||||||
composite types. Usually you'll want to return rows one at a
|
composite types. Usually you'll want to return rows one at a
|
||||||
|
@ -1870,6 +1870,22 @@ SELECT * FROM get_available_flightid(CURRENT_DATE);
|
|||||||
then <symbol>NULL</symbol> must be returned. Returning any other value
|
then <symbol>NULL</symbol> must be returned. Returning any other value
|
||||||
will result in an error.
|
will result in an error.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If a procedure has output parameters, then the output values can be
|
||||||
|
assigned to the parameters as if they were variables. For example:
|
||||||
|
<programlisting>
|
||||||
|
CREATE PROCEDURE triple(INOUT x int)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
x := x * 3;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL triple(5);
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
|
|
||||||
<sect2 id="plpgsql-conditionals">
|
<sect2 id="plpgsql-conditionals">
|
||||||
|
@ -649,6 +649,17 @@ return (1, 2)
|
|||||||
$$ LANGUAGE plpythonu;
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
SELECT * FROM multiout_simple();
|
SELECT * FROM multiout_simple();
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Output parameters of procedures are passed back the same way. For example:
|
||||||
|
<programlisting>
|
||||||
|
CREATE PROCEDURE python_triple(INOUT a integer, INOUT b integer) AS $$
|
||||||
|
return (a * 3, b * 3)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CALL python_triple(5, 10);
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
|
@ -186,6 +186,18 @@ $$ LANGUAGE pltcl;
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Output arguments of procedures are returned in the same way, for example:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE PROCEDURE tcl_triple(INOUT a integer, INOUT b integer) AS $$
|
||||||
|
return [list a [expr {$1 * 3}] b [expr {$2 * 3}]]
|
||||||
|
$$ LANGUAGE pltcl;
|
||||||
|
|
||||||
|
CALL tcl_triple(5, 10);
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
|
||||||
<tip>
|
<tip>
|
||||||
<para>
|
<para>
|
||||||
The result list can be made from an array representation of the
|
The result list can be made from an array representation of the
|
||||||
|
@ -31,6 +31,10 @@ CALL <replaceable class="parameter">name</replaceable> ( [ <replaceable class="p
|
|||||||
<para>
|
<para>
|
||||||
<command>CALL</command> executes a procedure.
|
<command>CALL</command> executes a procedure.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If the procedure has output arguments, then a result row will be returned.
|
||||||
|
</para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
<refsect1>
|
<refsect1>
|
||||||
|
@ -96,8 +96,11 @@ CREATE [ OR REPLACE ] PROCEDURE
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The mode of an argument: <literal>IN</literal> or <literal>VARIADIC</literal>.
|
The mode of an argument: <literal>IN</literal>,
|
||||||
If omitted, the default is <literal>IN</literal>.
|
<literal>INOUT</literal>, or <literal>VARIADIC</literal>. If omitted,
|
||||||
|
the default is <literal>IN</literal>. (<literal>OUT</literal>
|
||||||
|
arguments are currently not supported for procedures. Use
|
||||||
|
<literal>INOUT</literal> instead.)
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -438,7 +438,8 @@ ProcedureCreate(const char *procedureName,
|
|||||||
TupleDesc newdesc;
|
TupleDesc newdesc;
|
||||||
|
|
||||||
olddesc = build_function_result_tupdesc_t(oldtup);
|
olddesc = build_function_result_tupdesc_t(oldtup);
|
||||||
newdesc = build_function_result_tupdesc_d(allParameterTypes,
|
newdesc = build_function_result_tupdesc_d(prokind,
|
||||||
|
allParameterTypes,
|
||||||
parameterModes,
|
parameterModes,
|
||||||
parameterNames);
|
parameterNames);
|
||||||
if (olddesc == NULL && newdesc == NULL)
|
if (olddesc == NULL && newdesc == NULL)
|
||||||
@ -925,6 +926,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
|
|||||||
querytree_sublist);
|
querytree_sublist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_sql_fn_statements(querytree_list);
|
||||||
(void) check_sql_fn_retval(funcoid, proc->prorettype,
|
(void) check_sql_fn_retval(funcoid, proc->prorettype,
|
||||||
querytree_list,
|
querytree_list,
|
||||||
NULL, NULL);
|
NULL, NULL);
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
#include "utils/typcache.h"
|
||||||
#include "utils/tqual.h"
|
#include "utils/tqual.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -281,10 +282,11 @@ interpret_function_parameter_list(ParseState *pstate,
|
|||||||
|
|
||||||
if (objtype == OBJECT_PROCEDURE)
|
if (objtype == OBJECT_PROCEDURE)
|
||||||
{
|
{
|
||||||
if (fp->mode == FUNC_PARAM_OUT || fp->mode == FUNC_PARAM_INOUT)
|
if (fp->mode == FUNC_PARAM_OUT)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
(errmsg("procedures cannot have OUT parameters"))));
|
(errmsg("procedures cannot have OUT arguments"),
|
||||||
|
errhint("INOUT arguments are permitted."))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* handle input parameters */
|
/* handle input parameters */
|
||||||
@ -302,7 +304,9 @@ interpret_function_parameter_list(ParseState *pstate,
|
|||||||
/* handle output parameters */
|
/* handle output parameters */
|
||||||
if (fp->mode != FUNC_PARAM_IN && fp->mode != FUNC_PARAM_VARIADIC)
|
if (fp->mode != FUNC_PARAM_IN && fp->mode != FUNC_PARAM_VARIADIC)
|
||||||
{
|
{
|
||||||
if (outCount == 0) /* save first output param's type */
|
if (objtype == OBJECT_PROCEDURE)
|
||||||
|
*requiredResultType = RECORDOID;
|
||||||
|
else if (outCount == 0) /* save first output param's type */
|
||||||
*requiredResultType = toid;
|
*requiredResultType = toid;
|
||||||
outCount++;
|
outCount++;
|
||||||
}
|
}
|
||||||
@ -1003,12 +1007,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
|
|||||||
|
|
||||||
if (stmt->is_procedure)
|
if (stmt->is_procedure)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* Sometime in the future, procedures might be allowed to return
|
|
||||||
* results; for now, they all return VOID.
|
|
||||||
*/
|
|
||||||
Assert(!stmt->returnType);
|
Assert(!stmt->returnType);
|
||||||
prorettype = VOIDOID;
|
prorettype = requiredResultType ? requiredResultType : VOIDOID;
|
||||||
returnsSet = false;
|
returnsSet = false;
|
||||||
}
|
}
|
||||||
else if (stmt->returnType)
|
else if (stmt->returnType)
|
||||||
@ -2206,7 +2206,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
|
|||||||
* commits that might occur inside the procedure.
|
* commits that might occur inside the procedure.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
|
ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver *dest)
|
||||||
{
|
{
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
FuncExpr *fexpr;
|
FuncExpr *fexpr;
|
||||||
@ -2219,6 +2219,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
|
|||||||
EState *estate;
|
EState *estate;
|
||||||
ExprContext *econtext;
|
ExprContext *econtext;
|
||||||
HeapTuple tp;
|
HeapTuple tp;
|
||||||
|
Datum retval;
|
||||||
|
|
||||||
fexpr = stmt->funcexpr;
|
fexpr = stmt->funcexpr;
|
||||||
Assert(fexpr);
|
Assert(fexpr);
|
||||||
@ -2285,7 +2286,51 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionCallInvoke(&fcinfo);
|
retval = FunctionCallInvoke(&fcinfo);
|
||||||
|
|
||||||
|
if (fexpr->funcresulttype == VOIDOID)
|
||||||
|
{
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
else if (fexpr->funcresulttype == RECORDOID)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* send tuple to client
|
||||||
|
*/
|
||||||
|
|
||||||
|
HeapTupleHeader td;
|
||||||
|
Oid tupType;
|
||||||
|
int32 tupTypmod;
|
||||||
|
TupleDesc retdesc;
|
||||||
|
HeapTupleData rettupdata;
|
||||||
|
TupOutputState *tstate;
|
||||||
|
TupleTableSlot *slot;
|
||||||
|
|
||||||
|
if (fcinfo.isnull)
|
||||||
|
elog(ERROR, "procedure returned null record");
|
||||||
|
|
||||||
|
td = DatumGetHeapTupleHeader(retval);
|
||||||
|
tupType = HeapTupleHeaderGetTypeId(td);
|
||||||
|
tupTypmod = HeapTupleHeaderGetTypMod(td);
|
||||||
|
retdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
|
||||||
|
|
||||||
|
tstate = begin_tup_output_tupdesc(dest, retdesc);
|
||||||
|
|
||||||
|
rettupdata.t_len = HeapTupleHeaderGetDatumLength(td);
|
||||||
|
ItemPointerSetInvalid(&(rettupdata.t_self));
|
||||||
|
rettupdata.t_tableOid = InvalidOid;
|
||||||
|
rettupdata.t_data = td;
|
||||||
|
|
||||||
|
slot = ExecStoreTuple(&rettupdata, tstate->slot, InvalidBuffer, false);
|
||||||
|
tstate->dest->receiveSlot(slot, tstate->dest);
|
||||||
|
|
||||||
|
end_tup_output(tstate);
|
||||||
|
|
||||||
|
ReleaseTupleDesc(retdesc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
elog(ERROR, "unexpected result type for procedure: %u",
|
||||||
|
fexpr->funcresulttype);
|
||||||
|
|
||||||
FreeExecutorState(estate);
|
FreeExecutorState(estate);
|
||||||
}
|
}
|
||||||
|
@ -721,6 +721,8 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
|
|||||||
list_copy(queryTree_sublist));
|
list_copy(queryTree_sublist));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_sql_fn_statements(flat_query_list);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check that the function returns the type it claims to. Although in
|
* Check that the function returns the type it claims to. Although in
|
||||||
* simple cases this was already done when the function was defined, we
|
* simple cases this was already done when the function was defined, we
|
||||||
@ -1486,6 +1488,55 @@ ShutdownSQLFunction(Datum arg)
|
|||||||
fcache->shutdown_reg = false;
|
fcache->shutdown_reg = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* check_sql_fn_statements
|
||||||
|
*
|
||||||
|
* Check statements in an SQL function. Error out if there is anything that
|
||||||
|
* is not acceptable.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
check_sql_fn_statements(List *queryTreeList)
|
||||||
|
{
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
foreach(lc, queryTreeList)
|
||||||
|
{
|
||||||
|
Query *query = lfirst_node(Query, lc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Disallow 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 preserve an
|
||||||
|
* opportunity for future improvement.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* check_sql_fn_retval() -- check return value of a list of sql parse trees.
|
* check_sql_fn_retval() -- check return value of a list of sql parse trees.
|
||||||
|
@ -661,7 +661,8 @@ standard_ProcessUtility(PlannedStmt *pstmt,
|
|||||||
|
|
||||||
case T_CallStmt:
|
case T_CallStmt:
|
||||||
ExecuteCallStmt(castNode(CallStmt, parsetree), params,
|
ExecuteCallStmt(castNode(CallStmt, parsetree), params,
|
||||||
(context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()));
|
(context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()),
|
||||||
|
dest);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_ClusterStmt:
|
case T_ClusterStmt:
|
||||||
|
@ -1205,7 +1205,8 @@ build_function_result_tupdesc_t(HeapTuple procTuple)
|
|||||||
if (isnull)
|
if (isnull)
|
||||||
proargnames = PointerGetDatum(NULL); /* just to be sure */
|
proargnames = PointerGetDatum(NULL); /* just to be sure */
|
||||||
|
|
||||||
return build_function_result_tupdesc_d(proallargtypes,
|
return build_function_result_tupdesc_d(procform->prokind,
|
||||||
|
proallargtypes,
|
||||||
proargmodes,
|
proargmodes,
|
||||||
proargnames);
|
proargnames);
|
||||||
}
|
}
|
||||||
@ -1218,10 +1219,12 @@ build_function_result_tupdesc_t(HeapTuple procTuple)
|
|||||||
* convenience of ProcedureCreate, which needs to be able to compute the
|
* convenience of ProcedureCreate, which needs to be able to compute the
|
||||||
* tupledesc before actually creating the function.
|
* tupledesc before actually creating the function.
|
||||||
*
|
*
|
||||||
* Returns NULL if there are not at least two OUT or INOUT arguments.
|
* For functions (but not for procedures), returns NULL if there are not at
|
||||||
|
* least two OUT or INOUT arguments.
|
||||||
*/
|
*/
|
||||||
TupleDesc
|
TupleDesc
|
||||||
build_function_result_tupdesc_d(Datum proallargtypes,
|
build_function_result_tupdesc_d(char prokind,
|
||||||
|
Datum proallargtypes,
|
||||||
Datum proargmodes,
|
Datum proargmodes,
|
||||||
Datum proargnames)
|
Datum proargnames)
|
||||||
{
|
{
|
||||||
@ -1311,7 +1314,7 @@ build_function_result_tupdesc_d(Datum proallargtypes,
|
|||||||
* If there is no output argument, or only one, the function does not
|
* If there is no output argument, or only one, the function does not
|
||||||
* return tuples.
|
* return tuples.
|
||||||
*/
|
*/
|
||||||
if (numoutargs < 2)
|
if (numoutargs < 2 && prokind != PROKIND_PROCEDURE)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
desc = CreateTemplateTupleDesc(numoutargs, false);
|
desc = CreateTemplateTupleDesc(numoutargs, false);
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include "catalog/objectaddress.h"
|
#include "catalog/objectaddress.h"
|
||||||
#include "nodes/params.h"
|
#include "nodes/params.h"
|
||||||
#include "nodes/parsenodes.h"
|
#include "nodes/parsenodes.h"
|
||||||
|
#include "tcop/dest.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
|
|
||||||
/* commands/dropcmds.c */
|
/* commands/dropcmds.c */
|
||||||
@ -62,7 +63,7 @@ extern void DropTransformById(Oid transformOid);
|
|||||||
extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
|
extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
|
||||||
oidvector *proargtypes, Oid nspOid);
|
oidvector *proargtypes, Oid nspOid);
|
||||||
extern void ExecuteDoStmt(DoStmt *stmt, bool atomic);
|
extern void ExecuteDoStmt(DoStmt *stmt, bool atomic);
|
||||||
extern void ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic);
|
extern void ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver *dest);
|
||||||
extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok);
|
extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok);
|
||||||
extern Oid get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok);
|
extern Oid get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok);
|
||||||
extern void interpret_function_parameter_list(ParseState *pstate,
|
extern void interpret_function_parameter_list(ParseState *pstate,
|
||||||
|
@ -29,6 +29,8 @@ extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTupl
|
|||||||
extern void sql_fn_parser_setup(struct ParseState *pstate,
|
extern void sql_fn_parser_setup(struct ParseState *pstate,
|
||||||
SQLFunctionParseInfoPtr pinfo);
|
SQLFunctionParseInfoPtr pinfo);
|
||||||
|
|
||||||
|
extern void check_sql_fn_statements(List *queryTreeList);
|
||||||
|
|
||||||
extern bool check_sql_fn_retval(Oid func_id, Oid rettype,
|
extern bool check_sql_fn_retval(Oid func_id, Oid rettype,
|
||||||
List *queryTreeList,
|
List *queryTreeList,
|
||||||
bool *modifyTargetList,
|
bool *modifyTargetList,
|
||||||
|
@ -187,7 +187,8 @@ extern int get_func_input_arg_names(Datum proargnames, Datum proargmodes,
|
|||||||
extern int get_func_trftypes(HeapTuple procTup, Oid **p_trftypes);
|
extern int get_func_trftypes(HeapTuple procTup, Oid **p_trftypes);
|
||||||
extern char *get_func_result_name(Oid functionId);
|
extern char *get_func_result_name(Oid functionId);
|
||||||
|
|
||||||
extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
|
extern TupleDesc build_function_result_tupdesc_d(char prokind,
|
||||||
|
Datum proallargtypes,
|
||||||
Datum proargmodes,
|
Datum proargmodes,
|
||||||
Datum proargnames);
|
Datum proargnames);
|
||||||
extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
|
extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
|
||||||
|
@ -23,6 +23,31 @@ SELECT * FROM test1;
|
|||||||
55
|
55
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
CREATE PROCEDURE test_proc5(INOUT a text)
|
||||||
|
LANGUAGE plperl
|
||||||
|
AS $$
|
||||||
|
my ($a) = @_;
|
||||||
|
return { a => "$a+$a" };
|
||||||
|
$$;
|
||||||
|
CALL test_proc5('abc');
|
||||||
|
a
|
||||||
|
---------
|
||||||
|
abc+abc
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
|
||||||
|
LANGUAGE plperl
|
||||||
|
AS $$
|
||||||
|
my ($a, $b, $c) = @_;
|
||||||
|
return { b => $b * $a, c => $c * $a };
|
||||||
|
$$;
|
||||||
|
CALL test_proc6(2, 3, 4);
|
||||||
|
b | c
|
||||||
|
---+---
|
||||||
|
6 | 8
|
||||||
|
(1 row)
|
||||||
|
|
||||||
DROP PROCEDURE test_proc1;
|
DROP PROCEDURE test_proc1;
|
||||||
DROP PROCEDURE test_proc2;
|
DROP PROCEDURE test_proc2;
|
||||||
DROP PROCEDURE test_proc3;
|
DROP PROCEDURE test_proc3;
|
||||||
|
@ -29,6 +29,28 @@ CALL test_proc3(55);
|
|||||||
SELECT * FROM test1;
|
SELECT * FROM test1;
|
||||||
|
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc5(INOUT a text)
|
||||||
|
LANGUAGE plperl
|
||||||
|
AS $$
|
||||||
|
my ($a) = @_;
|
||||||
|
return { a => "$a+$a" };
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL test_proc5('abc');
|
||||||
|
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
|
||||||
|
LANGUAGE plperl
|
||||||
|
AS $$
|
||||||
|
my ($a, $b, $c) = @_;
|
||||||
|
return { b => $b * $a, c => $c * $a };
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL test_proc6(2, 3, 4);
|
||||||
|
|
||||||
|
|
||||||
DROP PROCEDURE test_proc1;
|
DROP PROCEDURE test_proc1;
|
||||||
DROP PROCEDURE test_proc2;
|
DROP PROCEDURE test_proc2;
|
||||||
DROP PROCEDURE test_proc3;
|
DROP PROCEDURE test_proc3;
|
||||||
|
@ -53,6 +53,118 @@ SELECT * FROM test1;
|
|||||||
66
|
66
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
CREATE PROCEDURE test_proc5(INOUT a text)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
a := a || '+' || a;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
CALL test_proc5('abc');
|
||||||
|
a
|
||||||
|
---------
|
||||||
|
abc+abc
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
b := b * a;
|
||||||
|
c := c * a;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
CALL test_proc6(2, 3, 4);
|
||||||
|
b | c
|
||||||
|
---+---
|
||||||
|
6 | 8
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DO
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
x int := 3;
|
||||||
|
y int := 4;
|
||||||
|
BEGIN
|
||||||
|
CALL test_proc6(2, x, y);
|
||||||
|
RAISE INFO 'x = %, y = %', x, y;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
INFO: x = 6, y = 8
|
||||||
|
DO
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
x int := 3;
|
||||||
|
y int := 4;
|
||||||
|
BEGIN
|
||||||
|
CALL test_proc6(2, x + 1, y); -- error
|
||||||
|
RAISE INFO 'x = %, y = %', x, y;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
ERROR: argument 2 is an output argument but is not writable
|
||||||
|
CONTEXT: PL/pgSQL function inline_code_block line 6 at CALL
|
||||||
|
DO
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
x int := 3;
|
||||||
|
y int := 4;
|
||||||
|
BEGIN
|
||||||
|
FOR i IN 1..5 LOOP
|
||||||
|
CALL test_proc6(i, x, y);
|
||||||
|
RAISE INFO 'x = %, y = %', x, y;
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
INFO: x = 3, y = 4
|
||||||
|
INFO: x = 6, y = 8
|
||||||
|
INFO: x = 18, y = 24
|
||||||
|
INFO: x = 72, y = 96
|
||||||
|
INFO: x = 360, y = 480
|
||||||
|
-- recursive with output arguments
|
||||||
|
CREATE PROCEDURE test_proc7(x int, INOUT a int, INOUT b numeric)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF x > 1 THEN
|
||||||
|
a := x / 10;
|
||||||
|
b := x / 2;
|
||||||
|
CALL test_proc7(b::int, a, b);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
CALL test_proc7(100, -1, -1);
|
||||||
|
a | b
|
||||||
|
---+---
|
||||||
|
0 | 1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- transition variable assignment
|
||||||
|
TRUNCATE test1;
|
||||||
|
CREATE FUNCTION triggerfunc1() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
z int := 0;
|
||||||
|
BEGIN
|
||||||
|
CALL test_proc6(2, NEW.a, NEW.a);
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
CREATE TRIGGER t1 BEFORE INSERT ON test1 EXECUTE PROCEDURE triggerfunc1();
|
||||||
|
INSERT INTO test1 VALUES (1), (2), (3);
|
||||||
|
UPDATE test1 SET a = 22 WHERE a = 2;
|
||||||
|
SELECT * FROM test1 ORDER BY a;
|
||||||
|
a
|
||||||
|
----
|
||||||
|
1
|
||||||
|
3
|
||||||
|
22
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
DROP PROCEDURE test_proc1;
|
DROP PROCEDURE test_proc1;
|
||||||
DROP PROCEDURE test_proc3;
|
DROP PROCEDURE test_proc3;
|
||||||
DROP PROCEDURE test_proc4;
|
DROP PROCEDURE test_proc4;
|
||||||
|
@ -98,7 +98,7 @@ SELECT transaction_test3();
|
|||||||
ERROR: invalid transaction termination
|
ERROR: invalid transaction termination
|
||||||
CONTEXT: PL/pgSQL function transaction_test1() line 6 at COMMIT
|
CONTEXT: PL/pgSQL function transaction_test1() line 6 at COMMIT
|
||||||
SQL statement "CALL transaction_test1()"
|
SQL statement "CALL transaction_test1()"
|
||||||
PL/pgSQL function transaction_test3() line 3 at SQL statement
|
PL/pgSQL function transaction_test3() line 3 at CALL
|
||||||
SELECT * FROM test1;
|
SELECT * FROM test1;
|
||||||
a | b
|
a | b
|
||||||
---+---
|
---+---
|
||||||
|
@ -475,11 +475,11 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
/*
|
/*
|
||||||
* If there's just one OUT parameter, out_param_varno points
|
* If there's just one OUT parameter, out_param_varno points
|
||||||
* directly to it. If there's more than one, build a row that
|
* directly to it. If there's more than one, build a row that
|
||||||
* holds all of them.
|
* holds all of them. Procedures return a row even for one OUT
|
||||||
|
* parameter.
|
||||||
*/
|
*/
|
||||||
if (num_out_args == 1)
|
if (num_out_args > 1 ||
|
||||||
function->out_param_varno = out_arg_variables[0]->dno;
|
(num_out_args == 1 && function->fn_prokind == PROKIND_PROCEDURE))
|
||||||
else if (num_out_args > 1)
|
|
||||||
{
|
{
|
||||||
PLpgSQL_row *row = build_row_from_vars(out_arg_variables,
|
PLpgSQL_row *row = build_row_from_vars(out_arg_variables,
|
||||||
num_out_args);
|
num_out_args);
|
||||||
@ -487,6 +487,8 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
plpgsql_adddatum((PLpgSQL_datum *) row);
|
plpgsql_adddatum((PLpgSQL_datum *) row);
|
||||||
function->out_param_varno = row->dno;
|
function->out_param_varno = row->dno;
|
||||||
}
|
}
|
||||||
|
else if (num_out_args == 1)
|
||||||
|
function->out_param_varno = out_arg_variables[0]->dno;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check for a polymorphic returntype. If found, use the actual
|
* Check for a polymorphic returntype. If found, use the actual
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "executor/execExpr.h"
|
#include "executor/execExpr.h"
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
|
#include "executor/spi_priv.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "nodes/nodeFuncs.h"
|
#include "nodes/nodeFuncs.h"
|
||||||
@ -40,6 +41,7 @@
|
|||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
#include "utils/snapmgr.h"
|
#include "utils/snapmgr.h"
|
||||||
|
#include "utils/syscache.h"
|
||||||
#include "utils/typcache.h"
|
#include "utils/typcache.h"
|
||||||
|
|
||||||
#include "plpgsql.h"
|
#include "plpgsql.h"
|
||||||
@ -253,6 +255,8 @@ static int exec_stmt_assign(PLpgSQL_execstate *estate,
|
|||||||
PLpgSQL_stmt_assign *stmt);
|
PLpgSQL_stmt_assign *stmt);
|
||||||
static int exec_stmt_perform(PLpgSQL_execstate *estate,
|
static int exec_stmt_perform(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_stmt_perform *stmt);
|
PLpgSQL_stmt_perform *stmt);
|
||||||
|
static int exec_stmt_call(PLpgSQL_execstate *estate,
|
||||||
|
PLpgSQL_stmt_call *stmt);
|
||||||
static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
|
static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_stmt_getdiag *stmt);
|
PLpgSQL_stmt_getdiag *stmt);
|
||||||
static int exec_stmt_if(PLpgSQL_execstate *estate,
|
static int exec_stmt_if(PLpgSQL_execstate *estate,
|
||||||
@ -1901,6 +1905,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
|
|||||||
rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) stmt);
|
rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) stmt);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PLPGSQL_STMT_CALL:
|
||||||
|
rc = exec_stmt_call(estate, (PLpgSQL_stmt_call *) stmt);
|
||||||
|
break;
|
||||||
|
|
||||||
case PLPGSQL_STMT_GETDIAG:
|
case PLPGSQL_STMT_GETDIAG:
|
||||||
rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt);
|
rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt);
|
||||||
break;
|
break;
|
||||||
@ -2041,6 +2049,121 @@ exec_stmt_perform(PLpgSQL_execstate *estate, PLpgSQL_stmt_perform *stmt)
|
|||||||
return PLPGSQL_RC_OK;
|
return PLPGSQL_RC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* exec_stmt_call
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
|
||||||
|
{
|
||||||
|
PLpgSQL_expr *expr = stmt->expr;
|
||||||
|
ParamListInfo paramLI;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (expr->plan == NULL)
|
||||||
|
exec_prepare_plan(estate, expr, 0);
|
||||||
|
|
||||||
|
paramLI = setup_param_list(estate, expr);
|
||||||
|
|
||||||
|
rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
|
||||||
|
estate->readonly_func, 0);
|
||||||
|
|
||||||
|
if (rc < 0)
|
||||||
|
elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
|
||||||
|
expr->query, SPI_result_code_string(rc));
|
||||||
|
|
||||||
|
if (SPI_processed == 1)
|
||||||
|
{
|
||||||
|
SPITupleTable *tuptab = SPI_tuptable;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct a dummy target row based on the output arguments of the
|
||||||
|
* procedure call.
|
||||||
|
*/
|
||||||
|
if (!stmt->target)
|
||||||
|
{
|
||||||
|
Node *node;
|
||||||
|
ListCell *lc;
|
||||||
|
FuncExpr *funcexpr;
|
||||||
|
int i;
|
||||||
|
HeapTuple tuple;
|
||||||
|
int numargs;
|
||||||
|
Oid *argtypes;
|
||||||
|
char **argnames;
|
||||||
|
char *argmodes;
|
||||||
|
MemoryContext oldcontext;
|
||||||
|
PLpgSQL_row *row;
|
||||||
|
int nfields;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the original CallStmt
|
||||||
|
*/
|
||||||
|
node = linitial_node(Query, ((CachedPlanSource *) linitial(expr->plan->plancache_list))->query_list)->utilityStmt;
|
||||||
|
if (!IsA(node, CallStmt))
|
||||||
|
elog(ERROR, "returned row from not a CallStmt");
|
||||||
|
|
||||||
|
funcexpr = castNode(CallStmt, node)->funcexpr;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the argument modes
|
||||||
|
*/
|
||||||
|
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcexpr->funcid));
|
||||||
|
if (!HeapTupleIsValid(tuple))
|
||||||
|
elog(ERROR, "cache lookup failed for function %u", funcexpr->funcid);
|
||||||
|
numargs = get_func_arg_info(tuple, &argtypes, &argnames, &argmodes);
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
|
Assert(numargs == list_length(funcexpr->args));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct row
|
||||||
|
*/
|
||||||
|
oldcontext = MemoryContextSwitchTo(estate->func->fn_cxt);
|
||||||
|
|
||||||
|
row = palloc0(sizeof(*row));
|
||||||
|
row->dtype = PLPGSQL_DTYPE_ROW;
|
||||||
|
row->lineno = -1;
|
||||||
|
row->varnos = palloc(sizeof(int) * FUNC_MAX_ARGS);
|
||||||
|
|
||||||
|
nfields = 0;
|
||||||
|
i = 0;
|
||||||
|
foreach (lc, funcexpr->args)
|
||||||
|
{
|
||||||
|
Node *n = lfirst(lc);
|
||||||
|
|
||||||
|
if (argmodes && argmodes[i] == PROARGMODE_INOUT)
|
||||||
|
{
|
||||||
|
Param *param;
|
||||||
|
|
||||||
|
if (!IsA(n, Param))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("argument %d is an output argument but is not writable", i + 1)));
|
||||||
|
|
||||||
|
param = castNode(Param, n);
|
||||||
|
/* paramid is offset by 1 (see make_datum_param()) */
|
||||||
|
row->varnos[nfields++] = param->paramid - 1;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
row->nfields = nfields;
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
|
stmt->target = (PLpgSQL_variable *) row;
|
||||||
|
}
|
||||||
|
|
||||||
|
exec_move_row(estate, stmt->target, tuptab->vals[0], tuptab->tupdesc);
|
||||||
|
}
|
||||||
|
else if (SPI_processed > 1)
|
||||||
|
elog(ERROR, "procedure call returned more than one row");
|
||||||
|
|
||||||
|
exec_eval_cleanup(estate);
|
||||||
|
SPI_freetuptable(SPI_tuptable);
|
||||||
|
|
||||||
|
return PLPGSQL_RC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* exec_stmt_getdiag Put internal PG information into
|
* exec_stmt_getdiag Put internal PG information into
|
||||||
* specified variables.
|
* specified variables.
|
||||||
@ -6763,7 +6886,7 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
elog(ERROR, "unsupported target");
|
elog(ERROR, "unsupported target type: %d", target->dtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -284,6 +284,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
|
|||||||
return "CLOSE";
|
return "CLOSE";
|
||||||
case PLPGSQL_STMT_PERFORM:
|
case PLPGSQL_STMT_PERFORM:
|
||||||
return "PERFORM";
|
return "PERFORM";
|
||||||
|
case PLPGSQL_STMT_CALL:
|
||||||
|
return "CALL";
|
||||||
case PLPGSQL_STMT_COMMIT:
|
case PLPGSQL_STMT_COMMIT:
|
||||||
return "COMMIT";
|
return "COMMIT";
|
||||||
case PLPGSQL_STMT_ROLLBACK:
|
case PLPGSQL_STMT_ROLLBACK:
|
||||||
@ -367,6 +369,7 @@ static void free_open(PLpgSQL_stmt_open *stmt);
|
|||||||
static void free_fetch(PLpgSQL_stmt_fetch *stmt);
|
static void free_fetch(PLpgSQL_stmt_fetch *stmt);
|
||||||
static void free_close(PLpgSQL_stmt_close *stmt);
|
static void free_close(PLpgSQL_stmt_close *stmt);
|
||||||
static void free_perform(PLpgSQL_stmt_perform *stmt);
|
static void free_perform(PLpgSQL_stmt_perform *stmt);
|
||||||
|
static void free_call(PLpgSQL_stmt_call *stmt);
|
||||||
static void free_commit(PLpgSQL_stmt_commit *stmt);
|
static void free_commit(PLpgSQL_stmt_commit *stmt);
|
||||||
static void free_rollback(PLpgSQL_stmt_rollback *stmt);
|
static void free_rollback(PLpgSQL_stmt_rollback *stmt);
|
||||||
static void free_expr(PLpgSQL_expr *expr);
|
static void free_expr(PLpgSQL_expr *expr);
|
||||||
@ -449,6 +452,9 @@ free_stmt(PLpgSQL_stmt *stmt)
|
|||||||
case PLPGSQL_STMT_PERFORM:
|
case PLPGSQL_STMT_PERFORM:
|
||||||
free_perform((PLpgSQL_stmt_perform *) stmt);
|
free_perform((PLpgSQL_stmt_perform *) stmt);
|
||||||
break;
|
break;
|
||||||
|
case PLPGSQL_STMT_CALL:
|
||||||
|
free_call((PLpgSQL_stmt_call *) stmt);
|
||||||
|
break;
|
||||||
case PLPGSQL_STMT_COMMIT:
|
case PLPGSQL_STMT_COMMIT:
|
||||||
free_commit((PLpgSQL_stmt_commit *) stmt);
|
free_commit((PLpgSQL_stmt_commit *) stmt);
|
||||||
break;
|
break;
|
||||||
@ -602,6 +608,12 @@ free_perform(PLpgSQL_stmt_perform *stmt)
|
|||||||
free_expr(stmt->expr);
|
free_expr(stmt->expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
free_call(PLpgSQL_stmt_call *stmt)
|
||||||
|
{
|
||||||
|
free_expr(stmt->expr);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
free_commit(PLpgSQL_stmt_commit *stmt)
|
free_commit(PLpgSQL_stmt_commit *stmt)
|
||||||
{
|
{
|
||||||
@ -805,6 +817,7 @@ static void dump_fetch(PLpgSQL_stmt_fetch *stmt);
|
|||||||
static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
|
static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
|
||||||
static void dump_close(PLpgSQL_stmt_close *stmt);
|
static void dump_close(PLpgSQL_stmt_close *stmt);
|
||||||
static void dump_perform(PLpgSQL_stmt_perform *stmt);
|
static void dump_perform(PLpgSQL_stmt_perform *stmt);
|
||||||
|
static void dump_call(PLpgSQL_stmt_call *stmt);
|
||||||
static void dump_commit(PLpgSQL_stmt_commit *stmt);
|
static void dump_commit(PLpgSQL_stmt_commit *stmt);
|
||||||
static void dump_rollback(PLpgSQL_stmt_rollback *stmt);
|
static void dump_rollback(PLpgSQL_stmt_rollback *stmt);
|
||||||
static void dump_expr(PLpgSQL_expr *expr);
|
static void dump_expr(PLpgSQL_expr *expr);
|
||||||
@ -897,6 +910,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
|
|||||||
case PLPGSQL_STMT_PERFORM:
|
case PLPGSQL_STMT_PERFORM:
|
||||||
dump_perform((PLpgSQL_stmt_perform *) stmt);
|
dump_perform((PLpgSQL_stmt_perform *) stmt);
|
||||||
break;
|
break;
|
||||||
|
case PLPGSQL_STMT_CALL:
|
||||||
|
dump_call((PLpgSQL_stmt_call *) stmt);
|
||||||
|
break;
|
||||||
case PLPGSQL_STMT_COMMIT:
|
case PLPGSQL_STMT_COMMIT:
|
||||||
dump_commit((PLpgSQL_stmt_commit *) stmt);
|
dump_commit((PLpgSQL_stmt_commit *) stmt);
|
||||||
break;
|
break;
|
||||||
@ -1275,6 +1291,15 @@ dump_perform(PLpgSQL_stmt_perform *stmt)
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dump_call(PLpgSQL_stmt_call *stmt)
|
||||||
|
{
|
||||||
|
dump_ind();
|
||||||
|
printf("CALL expr = ");
|
||||||
|
dump_expr(stmt->expr);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
dump_commit(PLpgSQL_stmt_commit *stmt)
|
dump_commit(PLpgSQL_stmt_commit *stmt)
|
||||||
{
|
{
|
||||||
|
@ -197,7 +197,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
|
|||||||
%type <stmt> proc_stmt pl_block
|
%type <stmt> proc_stmt pl_block
|
||||||
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
|
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
|
||||||
%type <stmt> stmt_return stmt_raise stmt_assert stmt_execsql
|
%type <stmt> stmt_return stmt_raise stmt_assert stmt_execsql
|
||||||
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag
|
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
|
||||||
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
|
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
|
||||||
%type <stmt> stmt_commit stmt_rollback
|
%type <stmt> stmt_commit stmt_rollback
|
||||||
%type <stmt> stmt_case stmt_foreach_a
|
%type <stmt> stmt_case stmt_foreach_a
|
||||||
@ -257,6 +257,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
|
|||||||
%token <keyword> K_BACKWARD
|
%token <keyword> K_BACKWARD
|
||||||
%token <keyword> K_BEGIN
|
%token <keyword> K_BEGIN
|
||||||
%token <keyword> K_BY
|
%token <keyword> K_BY
|
||||||
|
%token <keyword> K_CALL
|
||||||
%token <keyword> K_CASE
|
%token <keyword> K_CASE
|
||||||
%token <keyword> K_CLOSE
|
%token <keyword> K_CLOSE
|
||||||
%token <keyword> K_COLLATE
|
%token <keyword> K_COLLATE
|
||||||
@ -872,6 +873,8 @@ proc_stmt : pl_block ';'
|
|||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
| stmt_perform
|
| stmt_perform
|
||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
|
| stmt_call
|
||||||
|
{ $$ = $1; }
|
||||||
| stmt_getdiag
|
| stmt_getdiag
|
||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
| stmt_open
|
| stmt_open
|
||||||
@ -903,6 +906,19 @@ stmt_perform : K_PERFORM expr_until_semi
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
stmt_call : K_CALL
|
||||||
|
{
|
||||||
|
PLpgSQL_stmt_call *new;
|
||||||
|
|
||||||
|
new = palloc0(sizeof(PLpgSQL_stmt_call));
|
||||||
|
new->cmd_type = PLPGSQL_STMT_CALL;
|
||||||
|
new->lineno = plpgsql_location_to_lineno(@1);
|
||||||
|
new->expr = read_sql_stmt("CALL ");
|
||||||
|
|
||||||
|
$$ = (PLpgSQL_stmt *)new;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
stmt_assign : assign_var assign_operator expr_until_semi
|
stmt_assign : assign_var assign_operator expr_until_semi
|
||||||
{
|
{
|
||||||
PLpgSQL_stmt_assign *new;
|
PLpgSQL_stmt_assign *new;
|
||||||
@ -2401,6 +2417,7 @@ unreserved_keyword :
|
|||||||
| K_ARRAY
|
| K_ARRAY
|
||||||
| K_ASSERT
|
| K_ASSERT
|
||||||
| K_BACKWARD
|
| K_BACKWARD
|
||||||
|
| K_CALL
|
||||||
| K_CLOSE
|
| K_CLOSE
|
||||||
| K_COLLATE
|
| K_COLLATE
|
||||||
| K_COLUMN
|
| K_COLUMN
|
||||||
@ -3129,15 +3146,6 @@ make_return_stmt(int location)
|
|||||||
errhint("Use RETURN NEXT or RETURN QUERY."),
|
errhint("Use RETURN NEXT or RETURN QUERY."),
|
||||||
parser_errposition(yylloc)));
|
parser_errposition(yylloc)));
|
||||||
}
|
}
|
||||||
else if (plpgsql_curr_compile->out_param_varno >= 0)
|
|
||||||
{
|
|
||||||
if (yylex() != ';')
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
||||||
errmsg("RETURN cannot have a parameter in function with OUT parameters"),
|
|
||||||
parser_errposition(yylloc)));
|
|
||||||
new->retvarno = plpgsql_curr_compile->out_param_varno;
|
|
||||||
}
|
|
||||||
else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
|
else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
|
||||||
{
|
{
|
||||||
if (yylex() != ';')
|
if (yylex() != ';')
|
||||||
@ -3154,6 +3162,15 @@ make_return_stmt(int location)
|
|||||||
parser_errposition(yylloc)));
|
parser_errposition(yylloc)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (plpgsql_curr_compile->out_param_varno >= 0)
|
||||||
|
{
|
||||||
|
if (yylex() != ';')
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||||
|
errmsg("RETURN cannot have a parameter in function with OUT parameters"),
|
||||||
|
parser_errposition(yylloc)));
|
||||||
|
new->retvarno = plpgsql_curr_compile->out_param_varno;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -102,6 +102,7 @@ static const ScanKeyword unreserved_keywords[] = {
|
|||||||
PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
|
PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD)
|
PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
|
PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("call", K_CALL, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD)
|
PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
|
PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
|
||||||
|
@ -125,6 +125,7 @@ typedef enum PLpgSQL_stmt_type
|
|||||||
PLPGSQL_STMT_FETCH,
|
PLPGSQL_STMT_FETCH,
|
||||||
PLPGSQL_STMT_CLOSE,
|
PLPGSQL_STMT_CLOSE,
|
||||||
PLPGSQL_STMT_PERFORM,
|
PLPGSQL_STMT_PERFORM,
|
||||||
|
PLPGSQL_STMT_CALL,
|
||||||
PLPGSQL_STMT_COMMIT,
|
PLPGSQL_STMT_COMMIT,
|
||||||
PLPGSQL_STMT_ROLLBACK
|
PLPGSQL_STMT_ROLLBACK
|
||||||
} PLpgSQL_stmt_type;
|
} PLpgSQL_stmt_type;
|
||||||
@ -508,6 +509,17 @@ typedef struct PLpgSQL_stmt_perform
|
|||||||
PLpgSQL_expr *expr;
|
PLpgSQL_expr *expr;
|
||||||
} PLpgSQL_stmt_perform;
|
} PLpgSQL_stmt_perform;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CALL statement
|
||||||
|
*/
|
||||||
|
typedef struct PLpgSQL_stmt_call
|
||||||
|
{
|
||||||
|
PLpgSQL_stmt_type cmd_type;
|
||||||
|
int lineno;
|
||||||
|
PLpgSQL_expr *expr;
|
||||||
|
PLpgSQL_variable *target;
|
||||||
|
} PLpgSQL_stmt_call;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* COMMIT statement
|
* COMMIT statement
|
||||||
*/
|
*/
|
||||||
|
@ -55,6 +55,113 @@ CALL test_proc4(66);
|
|||||||
SELECT * FROM test1;
|
SELECT * FROM test1;
|
||||||
|
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc5(INOUT a text)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
a := a || '+' || a;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL test_proc5('abc');
|
||||||
|
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
b := b * a;
|
||||||
|
c := c * a;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL test_proc6(2, 3, 4);
|
||||||
|
|
||||||
|
|
||||||
|
DO
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
x int := 3;
|
||||||
|
y int := 4;
|
||||||
|
BEGIN
|
||||||
|
CALL test_proc6(2, x, y);
|
||||||
|
RAISE INFO 'x = %, y = %', x, y;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
DO
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
x int := 3;
|
||||||
|
y int := 4;
|
||||||
|
BEGIN
|
||||||
|
CALL test_proc6(2, x + 1, y); -- error
|
||||||
|
RAISE INFO 'x = %, y = %', x, y;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
DO
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
x int := 3;
|
||||||
|
y int := 4;
|
||||||
|
BEGIN
|
||||||
|
FOR i IN 1..5 LOOP
|
||||||
|
CALL test_proc6(i, x, y);
|
||||||
|
RAISE INFO 'x = %, y = %', x, y;
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
-- recursive with output arguments
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc7(x int, INOUT a int, INOUT b numeric)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
IF x > 1 THEN
|
||||||
|
a := x / 10;
|
||||||
|
b := x / 2;
|
||||||
|
CALL test_proc7(b::int, a, b);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL test_proc7(100, -1, -1);
|
||||||
|
|
||||||
|
|
||||||
|
-- transition variable assignment
|
||||||
|
|
||||||
|
TRUNCATE test1;
|
||||||
|
|
||||||
|
CREATE FUNCTION triggerfunc1() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
z int := 0;
|
||||||
|
BEGIN
|
||||||
|
CALL test_proc6(2, NEW.a, NEW.a);
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE TRIGGER t1 BEFORE INSERT ON test1 EXECUTE PROCEDURE triggerfunc1();
|
||||||
|
|
||||||
|
INSERT INTO test1 VALUES (1), (2), (3);
|
||||||
|
|
||||||
|
UPDATE test1 SET a = 22 WHERE a = 2;
|
||||||
|
|
||||||
|
SELECT * FROM test1 ORDER BY a;
|
||||||
|
|
||||||
|
|
||||||
DROP PROCEDURE test_proc1;
|
DROP PROCEDURE test_proc1;
|
||||||
DROP PROCEDURE test_proc3;
|
DROP PROCEDURE test_proc3;
|
||||||
DROP PROCEDURE test_proc4;
|
DROP PROCEDURE test_proc4;
|
||||||
|
@ -29,6 +29,29 @@ SELECT * FROM test1;
|
|||||||
55
|
55
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
CREATE PROCEDURE test_proc5(INOUT a text)
|
||||||
|
LANGUAGE plpythonu
|
||||||
|
AS $$
|
||||||
|
return [a + '+' + a]
|
||||||
|
$$;
|
||||||
|
CALL test_proc5('abc');
|
||||||
|
a
|
||||||
|
---------
|
||||||
|
abc+abc
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
|
||||||
|
LANGUAGE plpythonu
|
||||||
|
AS $$
|
||||||
|
return (b * a, c * a)
|
||||||
|
$$;
|
||||||
|
CALL test_proc6(2, 3, 4);
|
||||||
|
b | c
|
||||||
|
---+---
|
||||||
|
6 | 8
|
||||||
|
(1 row)
|
||||||
|
|
||||||
DROP PROCEDURE test_proc1;
|
DROP PROCEDURE test_proc1;
|
||||||
DROP PROCEDURE test_proc2;
|
DROP PROCEDURE test_proc2;
|
||||||
DROP PROCEDURE test_proc3;
|
DROP PROCEDURE test_proc3;
|
||||||
|
@ -204,21 +204,19 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
|
|||||||
* return value as a special "void datum" rather than NULL (as is the
|
* return value as a special "void datum" rather than NULL (as is the
|
||||||
* case for non-void-returning functions).
|
* case for non-void-returning functions).
|
||||||
*/
|
*/
|
||||||
if (proc->is_procedure)
|
if (proc->result.typoid == VOIDOID)
|
||||||
{
|
{
|
||||||
if (plrv != Py_None)
|
if (plrv != Py_None)
|
||||||
ereport(ERROR,
|
{
|
||||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
if (proc->is_procedure)
|
||||||
errmsg("PL/Python procedure did not return None")));
|
ereport(ERROR,
|
||||||
fcinfo->isnull = false;
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||||
rv = (Datum) 0;
|
errmsg("PL/Python procedure did not return None")));
|
||||||
}
|
else
|
||||||
else if (proc->result.typoid == VOIDOID)
|
ereport(ERROR,
|
||||||
{
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||||
if (plrv != Py_None)
|
errmsg("PL/Python function with return type \"void\" did not return None")));
|
||||||
ereport(ERROR,
|
}
|
||||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
||||||
errmsg("PL/Python function with return type \"void\" did not return None")));
|
|
||||||
|
|
||||||
fcinfo->isnull = false;
|
fcinfo->isnull = false;
|
||||||
rv = (Datum) 0;
|
rv = (Datum) 0;
|
||||||
|
@ -34,6 +34,26 @@ CALL test_proc3(55);
|
|||||||
SELECT * FROM test1;
|
SELECT * FROM test1;
|
||||||
|
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc5(INOUT a text)
|
||||||
|
LANGUAGE plpythonu
|
||||||
|
AS $$
|
||||||
|
return [a + '+' + a]
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL test_proc5('abc');
|
||||||
|
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
|
||||||
|
LANGUAGE plpythonu
|
||||||
|
AS $$
|
||||||
|
return (b * a, c * a)
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL test_proc6(2, 3, 4);
|
||||||
|
|
||||||
|
|
||||||
DROP PROCEDURE test_proc1;
|
DROP PROCEDURE test_proc1;
|
||||||
DROP PROCEDURE test_proc2;
|
DROP PROCEDURE test_proc2;
|
||||||
DROP PROCEDURE test_proc3;
|
DROP PROCEDURE test_proc3;
|
||||||
|
@ -23,6 +23,32 @@ SELECT * FROM test1;
|
|||||||
55
|
55
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
CREATE PROCEDURE test_proc5(INOUT a text)
|
||||||
|
LANGUAGE pltcl
|
||||||
|
AS $$
|
||||||
|
set aa [concat $1 "+" $1]
|
||||||
|
return [list a $aa]
|
||||||
|
$$;
|
||||||
|
CALL test_proc5('abc');
|
||||||
|
a
|
||||||
|
-----------
|
||||||
|
abc + abc
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
|
||||||
|
LANGUAGE pltcl
|
||||||
|
AS $$
|
||||||
|
set bb [expr $2 * $1]
|
||||||
|
set cc [expr $3 * $1]
|
||||||
|
return [list b $bb c $cc]
|
||||||
|
$$;
|
||||||
|
CALL test_proc6(2, 3, 4);
|
||||||
|
b | c
|
||||||
|
---+---
|
||||||
|
6 | 8
|
||||||
|
(1 row)
|
||||||
|
|
||||||
DROP PROCEDURE test_proc1;
|
DROP PROCEDURE test_proc1;
|
||||||
DROP PROCEDURE test_proc2;
|
DROP PROCEDURE test_proc2;
|
||||||
DROP PROCEDURE test_proc3;
|
DROP PROCEDURE test_proc3;
|
||||||
|
@ -29,6 +29,29 @@ CALL test_proc3(55);
|
|||||||
SELECT * FROM test1;
|
SELECT * FROM test1;
|
||||||
|
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc5(INOUT a text)
|
||||||
|
LANGUAGE pltcl
|
||||||
|
AS $$
|
||||||
|
set aa [concat $1 "+" $1]
|
||||||
|
return [list a $aa]
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL test_proc5('abc');
|
||||||
|
|
||||||
|
|
||||||
|
CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
|
||||||
|
LANGUAGE pltcl
|
||||||
|
AS $$
|
||||||
|
set bb [expr $2 * $1]
|
||||||
|
set cc [expr $3 * $1]
|
||||||
|
return [list b $bb c $cc]
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL test_proc6(2, 3, 4);
|
||||||
|
|
||||||
|
|
||||||
DROP PROCEDURE test_proc1;
|
DROP PROCEDURE test_proc1;
|
||||||
DROP PROCEDURE test_proc2;
|
DROP PROCEDURE test_proc2;
|
||||||
DROP PROCEDURE test_proc3;
|
DROP PROCEDURE test_proc3;
|
||||||
|
@ -71,6 +71,26 @@ SELECT * FROM cp_test;
|
|||||||
1 | b
|
1 | b
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
CREATE PROCEDURE ptest4a(INOUT a int, INOUT b int)
|
||||||
|
LANGUAGE SQL
|
||||||
|
AS $$
|
||||||
|
SELECT 1, 2;
|
||||||
|
$$;
|
||||||
|
CALL ptest4a(NULL, NULL);
|
||||||
|
a | b
|
||||||
|
---+---
|
||||||
|
1 | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE PROCEDURE ptest4b(INOUT b int, INOUT a int)
|
||||||
|
LANGUAGE SQL
|
||||||
|
AS $$
|
||||||
|
CALL ptest4a(a, b); -- error, not supported
|
||||||
|
$$;
|
||||||
|
ERROR: calling procedures with output arguments is not supported in SQL functions
|
||||||
|
CONTEXT: SQL function "ptest4b"
|
||||||
|
DROP PROCEDURE ptest4a;
|
||||||
-- various error cases
|
-- various error cases
|
||||||
CALL version(); -- error: not a procedure
|
CALL version(); -- error: not a procedure
|
||||||
ERROR: version() is not a procedure
|
ERROR: version() is not a procedure
|
||||||
@ -90,7 +110,8 @@ ERROR: invalid attribute in procedure definition
|
|||||||
LINE 1: CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT I...
|
LINE 1: CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT I...
|
||||||
^
|
^
|
||||||
CREATE PROCEDURE ptestx(OUT a int) LANGUAGE SQL AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
|
CREATE PROCEDURE ptestx(OUT a int) LANGUAGE SQL AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
|
||||||
ERROR: procedures cannot have OUT parameters
|
ERROR: procedures cannot have OUT arguments
|
||||||
|
HINT: INOUT arguments are permitted.
|
||||||
ALTER PROCEDURE ptest1(text) STRICT;
|
ALTER PROCEDURE ptest1(text) STRICT;
|
||||||
ERROR: invalid attribute in procedure definition
|
ERROR: invalid attribute in procedure definition
|
||||||
LINE 1: ALTER PROCEDURE ptest1(text) STRICT;
|
LINE 1: ALTER PROCEDURE ptest1(text) STRICT;
|
||||||
|
@ -46,6 +46,25 @@ CALL ptest3('b');
|
|||||||
SELECT * FROM cp_test;
|
SELECT * FROM cp_test;
|
||||||
|
|
||||||
|
|
||||||
|
-- output arguments
|
||||||
|
|
||||||
|
CREATE PROCEDURE ptest4a(INOUT a int, INOUT b int)
|
||||||
|
LANGUAGE SQL
|
||||||
|
AS $$
|
||||||
|
SELECT 1, 2;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CALL ptest4a(NULL, NULL);
|
||||||
|
|
||||||
|
CREATE PROCEDURE ptest4b(INOUT b int, INOUT a int)
|
||||||
|
LANGUAGE SQL
|
||||||
|
AS $$
|
||||||
|
CALL ptest4a(a, b); -- error, not supported
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DROP PROCEDURE ptest4a;
|
||||||
|
|
||||||
|
|
||||||
-- various error cases
|
-- various error cases
|
||||||
|
|
||||||
CALL version(); -- error: not a procedure
|
CALL version(); -- error: not a procedure
|
||||||
|
Reference in New Issue
Block a user