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:
Peter Eisentraut 2018-03-14 11:47:21 -04:00
parent 484a4a08ab
commit 33803f67f1
32 changed files with 792 additions and 50 deletions

View File

@ -278,6 +278,20 @@ SELECT * FROM perl_row();
hash will be returned as null values.
</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 =&gt; $a * 3, b =&gt; $b * 3};
$$ LANGUAGE plperl;
CALL perl_triple(5, 10);
</programlisting>
</para>
<para>
PL/Perl functions can also return sets of either scalar or
composite types. Usually you'll want to return rows one at a

View File

@ -1870,6 +1870,22 @@ SELECT * FROM get_available_flightid(CURRENT_DATE);
then <symbol>NULL</symbol> must be returned. Returning any other value
will result in an error.
</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 id="plpgsql-conditionals">

View File

@ -649,6 +649,17 @@ return (1, 2)
$$ LANGUAGE plpythonu;
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>
</para>
</sect2>

View File

@ -186,6 +186,18 @@ $$ LANGUAGE pltcl;
</programlisting>
</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>
<para>
The result list can be made from an array representation of the

View File

@ -31,6 +31,10 @@ CALL <replaceable class="parameter">name</replaceable> ( [ <replaceable class="p
<para>
<command>CALL</command> executes a procedure.
</para>
<para>
If the procedure has output arguments, then a result row will be returned.
</para>
</refsect1>
<refsect1>

View File

@ -96,8 +96,11 @@ CREATE [ OR REPLACE ] PROCEDURE
<listitem>
<para>
The mode of an argument: <literal>IN</literal> or <literal>VARIADIC</literal>.
If omitted, the default is <literal>IN</literal>.
The mode of an argument: <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>
</listitem>
</varlistentry>

View File

@ -438,7 +438,8 @@ ProcedureCreate(const char *procedureName,
TupleDesc newdesc;
olddesc = build_function_result_tupdesc_t(oldtup);
newdesc = build_function_result_tupdesc_d(allParameterTypes,
newdesc = build_function_result_tupdesc_d(prokind,
allParameterTypes,
parameterModes,
parameterNames);
if (olddesc == NULL && newdesc == NULL)
@ -925,6 +926,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
querytree_sublist);
}
check_sql_fn_statements(querytree_list);
(void) check_sql_fn_retval(funcoid, proc->prorettype,
querytree_list,
NULL, NULL);

View File

@ -68,6 +68,7 @@
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
#include "utils/tqual.h"
/*
@ -281,10 +282,11 @@ interpret_function_parameter_list(ParseState *pstate,
if (objtype == OBJECT_PROCEDURE)
{
if (fp->mode == FUNC_PARAM_OUT || fp->mode == FUNC_PARAM_INOUT)
if (fp->mode == FUNC_PARAM_OUT)
ereport(ERROR,
(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 */
@ -302,7 +304,9 @@ interpret_function_parameter_list(ParseState *pstate,
/* handle output parameters */
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;
outCount++;
}
@ -1003,12 +1007,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
if (stmt->is_procedure)
{
/*
* Sometime in the future, procedures might be allowed to return
* results; for now, they all return VOID.
*/
Assert(!stmt->returnType);
prorettype = VOIDOID;
prorettype = requiredResultType ? requiredResultType : VOIDOID;
returnsSet = false;
}
else if (stmt->returnType)
@ -2206,7 +2206,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
* commits that might occur inside the procedure.
*/
void
ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver *dest)
{
ListCell *lc;
FuncExpr *fexpr;
@ -2219,6 +2219,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
EState *estate;
ExprContext *econtext;
HeapTuple tp;
Datum retval;
fexpr = stmt->funcexpr;
Assert(fexpr);
@ -2285,7 +2286,51 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
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);
}

View File

@ -721,6 +721,8 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
list_copy(queryTree_sublist));
}
check_sql_fn_statements(flat_query_list);
/*
* Check that the function returns the type it claims to. Although in
* simple cases this was already done when the function was defined, we
@ -1486,6 +1488,55 @@ ShutdownSQLFunction(Datum arg)
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.

View File

@ -661,7 +661,8 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_CallStmt:
ExecuteCallStmt(castNode(CallStmt, parsetree), params,
(context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()));
(context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()),
dest);
break;
case T_ClusterStmt:

View File

@ -1205,7 +1205,8 @@ build_function_result_tupdesc_t(HeapTuple procTuple)
if (isnull)
proargnames = PointerGetDatum(NULL); /* just to be sure */
return build_function_result_tupdesc_d(proallargtypes,
return build_function_result_tupdesc_d(procform->prokind,
proallargtypes,
proargmodes,
proargnames);
}
@ -1218,10 +1219,12 @@ build_function_result_tupdesc_t(HeapTuple procTuple)
* convenience of ProcedureCreate, which needs to be able to compute the
* 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
build_function_result_tupdesc_d(Datum proallargtypes,
build_function_result_tupdesc_d(char prokind,
Datum proallargtypes,
Datum proargmodes,
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
* return tuples.
*/
if (numoutargs < 2)
if (numoutargs < 2 && prokind != PROKIND_PROCEDURE)
return NULL;
desc = CreateTemplateTupleDesc(numoutargs, false);

View File

@ -17,6 +17,7 @@
#include "catalog/objectaddress.h"
#include "nodes/params.h"
#include "nodes/parsenodes.h"
#include "tcop/dest.h"
#include "utils/array.h"
/* commands/dropcmds.c */
@ -62,7 +63,7 @@ extern void DropTransformById(Oid transformOid);
extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
oidvector *proargtypes, Oid nspOid);
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_transform_oid(Oid type_id, Oid lang_id, bool missing_ok);
extern void interpret_function_parameter_list(ParseState *pstate,

View File

@ -29,6 +29,8 @@ extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTupl
extern void sql_fn_parser_setup(struct ParseState *pstate,
SQLFunctionParseInfoPtr pinfo);
extern void check_sql_fn_statements(List *queryTreeList);
extern bool check_sql_fn_retval(Oid func_id, Oid rettype,
List *queryTreeList,
bool *modifyTargetList,

View File

@ -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 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 proargnames);
extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);

View File

@ -23,6 +23,31 @@ SELECT * FROM test1;
55
(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_proc2;
DROP PROCEDURE test_proc3;

View File

@ -29,6 +29,28 @@ CALL test_proc3(55);
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_proc2;
DROP PROCEDURE test_proc3;

View File

@ -53,6 +53,118 @@ SELECT * FROM test1;
66
(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_proc3;
DROP PROCEDURE test_proc4;

View File

@ -98,7 +98,7 @@ SELECT transaction_test3();
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function transaction_test1() line 6 at COMMIT
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;
a | b
---+---

View File

@ -475,11 +475,11 @@ do_compile(FunctionCallInfo fcinfo,
/*
* If there's just one OUT parameter, out_param_varno points
* 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)
function->out_param_varno = out_arg_variables[0]->dno;
else if (num_out_args > 1)
if (num_out_args > 1 ||
(num_out_args == 1 && function->fn_prokind == PROKIND_PROCEDURE))
{
PLpgSQL_row *row = build_row_from_vars(out_arg_variables,
num_out_args);
@ -487,6 +487,8 @@ do_compile(FunctionCallInfo fcinfo,
plpgsql_adddatum((PLpgSQL_datum *) row);
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

View File

@ -24,6 +24,7 @@
#include "catalog/pg_type.h"
#include "executor/execExpr.h"
#include "executor/spi.h"
#include "executor/spi_priv.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
@ -40,6 +41,7 @@
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
#include "plpgsql.h"
@ -253,6 +255,8 @@ static int exec_stmt_assign(PLpgSQL_execstate *estate,
PLpgSQL_stmt_assign *stmt);
static int exec_stmt_perform(PLpgSQL_execstate *estate,
PLpgSQL_stmt_perform *stmt);
static int exec_stmt_call(PLpgSQL_execstate *estate,
PLpgSQL_stmt_call *stmt);
static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
PLpgSQL_stmt_getdiag *stmt);
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);
break;
case PLPGSQL_STMT_CALL:
rc = exec_stmt_call(estate, (PLpgSQL_stmt_call *) stmt);
break;
case PLPGSQL_STMT_GETDIAG:
rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt);
break;
@ -2041,6 +2049,121 @@ exec_stmt_perform(PLpgSQL_execstate *estate, PLpgSQL_stmt_perform *stmt)
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
* specified variables.
@ -6763,7 +6886,7 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate,
return;
}
elog(ERROR, "unsupported target");
elog(ERROR, "unsupported target type: %d", target->dtype);
}
/*

View File

@ -284,6 +284,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
return "CLOSE";
case PLPGSQL_STMT_PERFORM:
return "PERFORM";
case PLPGSQL_STMT_CALL:
return "CALL";
case PLPGSQL_STMT_COMMIT:
return "COMMIT";
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_close(PLpgSQL_stmt_close *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_rollback(PLpgSQL_stmt_rollback *stmt);
static void free_expr(PLpgSQL_expr *expr);
@ -449,6 +452,9 @@ free_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_PERFORM:
free_perform((PLpgSQL_stmt_perform *) stmt);
break;
case PLPGSQL_STMT_CALL:
free_call((PLpgSQL_stmt_call *) stmt);
break;
case PLPGSQL_STMT_COMMIT:
free_commit((PLpgSQL_stmt_commit *) stmt);
break;
@ -602,6 +608,12 @@ free_perform(PLpgSQL_stmt_perform *stmt)
free_expr(stmt->expr);
}
static void
free_call(PLpgSQL_stmt_call *stmt)
{
free_expr(stmt->expr);
}
static void
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_close(PLpgSQL_stmt_close *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_rollback(PLpgSQL_stmt_rollback *stmt);
static void dump_expr(PLpgSQL_expr *expr);
@ -897,6 +910,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_PERFORM:
dump_perform((PLpgSQL_stmt_perform *) stmt);
break;
case PLPGSQL_STMT_CALL:
dump_call((PLpgSQL_stmt_call *) stmt);
break;
case PLPGSQL_STMT_COMMIT:
dump_commit((PLpgSQL_stmt_commit *) stmt);
break;
@ -1275,6 +1291,15 @@ dump_perform(PLpgSQL_stmt_perform *stmt)
printf("\n");
}
static void
dump_call(PLpgSQL_stmt_call *stmt)
{
dump_ind();
printf("CALL expr = ");
dump_expr(stmt->expr);
printf("\n");
}
static void
dump_commit(PLpgSQL_stmt_commit *stmt)
{

View File

@ -197,7 +197,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <stmt> proc_stmt pl_block
%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_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_commit stmt_rollback
%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_BEGIN
%token <keyword> K_BY
%token <keyword> K_CALL
%token <keyword> K_CASE
%token <keyword> K_CLOSE
%token <keyword> K_COLLATE
@ -872,6 +873,8 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_perform
{ $$ = $1; }
| stmt_call
{ $$ = $1; }
| stmt_getdiag
{ $$ = $1; }
| 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
{
PLpgSQL_stmt_assign *new;
@ -2401,6 +2417,7 @@ unreserved_keyword :
| K_ARRAY
| K_ASSERT
| K_BACKWARD
| K_CALL
| K_CLOSE
| K_COLLATE
| K_COLUMN
@ -3129,15 +3146,6 @@ make_return_stmt(int location)
errhint("Use RETURN NEXT or RETURN QUERY."),
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)
{
if (yylex() != ';')
@ -3154,6 +3162,15 @@ make_return_stmt(int location)
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
{
/*

View File

@ -102,6 +102,7 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
PG_KEYWORD("assert", K_ASSERT, 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("collate", K_COLLATE, UNRESERVED_KEYWORD)
PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)

View File

@ -125,6 +125,7 @@ typedef enum PLpgSQL_stmt_type
PLPGSQL_STMT_FETCH,
PLPGSQL_STMT_CLOSE,
PLPGSQL_STMT_PERFORM,
PLPGSQL_STMT_CALL,
PLPGSQL_STMT_COMMIT,
PLPGSQL_STMT_ROLLBACK
} PLpgSQL_stmt_type;
@ -508,6 +509,17 @@ typedef struct PLpgSQL_stmt_perform
PLpgSQL_expr *expr;
} 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
*/

View File

@ -55,6 +55,113 @@ CALL test_proc4(66);
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_proc3;
DROP PROCEDURE test_proc4;

View File

@ -29,6 +29,29 @@ SELECT * FROM test1;
55
(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_proc2;
DROP PROCEDURE test_proc3;

View File

@ -204,21 +204,19 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
* return value as a special "void datum" rather than NULL (as is the
* case for non-void-returning functions).
*/
if (proc->is_procedure)
if (proc->result.typoid == VOIDOID)
{
if (plrv != Py_None)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("PL/Python procedure did not return None")));
fcinfo->isnull = false;
rv = (Datum) 0;
}
else if (proc->result.typoid == VOIDOID)
{
if (plrv != Py_None)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("PL/Python function with return type \"void\" did not return None")));
{
if (proc->is_procedure)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("PL/Python procedure did not return None")));
else
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("PL/Python function with return type \"void\" did not return None")));
}
fcinfo->isnull = false;
rv = (Datum) 0;

View File

@ -34,6 +34,26 @@ CALL test_proc3(55);
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_proc2;
DROP PROCEDURE test_proc3;

View File

@ -23,6 +23,32 @@ SELECT * FROM test1;
55
(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_proc2;
DROP PROCEDURE test_proc3;

View File

@ -29,6 +29,29 @@ CALL test_proc3(55);
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_proc2;
DROP PROCEDURE test_proc3;

View File

@ -71,6 +71,26 @@ SELECT * FROM cp_test;
1 | b
(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
CALL version(); -- error: 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...
^
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;
ERROR: invalid attribute in procedure definition
LINE 1: ALTER PROCEDURE ptest1(text) STRICT;

View File

@ -46,6 +46,25 @@ CALL ptest3('b');
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
CALL version(); -- error: not a procedure