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:
parent
484a4a08ab
commit
33803f67f1
doc/src/sgml
src
backend
include
pl
test/regress
@ -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 => $a * 3, b => $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
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
---+---
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
/*
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user