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
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">
$$ 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>
</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
<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>
<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>
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)
querytree_sublist);
}
+ check_sql_fn_statements(querytree_list);
(void) check_sql_fn_retval(funcoid, proc->prorettype,
querytree_list,
NULL, NULL);
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
+#include "utils/typcache.h"
#include "utils/tqual.h"
/*
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 */
/* 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++;
}
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)
* 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;
EState *estate;
ExprContext *econtext;
HeapTuple tp;
+ Datum retval;
fexpr = stmt->funcexpr;
Assert(fexpr);
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);
}
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
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.
case T_CallStmt:
ExecuteCallStmt(castNode(CallStmt, parsetree), params,
- (context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()));
+ (context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()),
+ dest);
break;
case T_ClusterStmt:
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);
}
* 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)
{
* 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);
#include "catalog/objectaddress.h"
#include "nodes/params.h"
#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
#include "utils/array.h"
/* commands/dropcmds.c */
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,
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,
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);
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;
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;
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;
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
---+---
/*
* 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);
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
#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"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/syscache.h"
#include "utils/typcache.h"
#include "plpgsql.h"
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,
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;
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.
return;
}
- elog(ERROR, "unsupported target");
+ elog(ERROR, "unsupported target type: %d", target->dtype);
}
/*
return "CLOSE";
case PLPGSQL_STMT_PERFORM:
return "PERFORM";
+ case PLPGSQL_STMT_CALL:
+ return "CALL";
case PLPGSQL_STMT_COMMIT:
return "COMMIT";
case PLPGSQL_STMT_ROLLBACK:
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);
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;
free_expr(stmt->expr);
}
+static void
+free_call(PLpgSQL_stmt_call *stmt)
+{
+ free_expr(stmt->expr);
+}
+
static void
free_commit(PLpgSQL_stmt_commit *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);
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;
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)
{
%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
%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
{ $$ = $1; }
| stmt_perform
{ $$ = $1; }
+ | stmt_call
+ { $$ = $1; }
| stmt_getdiag
{ $$ = $1; }
| stmt_open
}
;
+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;
| K_ARRAY
| K_ASSERT
| K_BACKWARD
+ | K_CALL
| K_CLOSE
| K_COLLATE
| K_COLUMN
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() != ';')
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
{
/*
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)
PLPGSQL_STMT_FETCH,
PLPGSQL_STMT_CLOSE,
PLPGSQL_STMT_PERFORM,
+ PLPGSQL_STMT_CALL,
PLPGSQL_STMT_COMMIT,
PLPGSQL_STMT_ROLLBACK
} PLpgSQL_stmt_type;
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
*/
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;
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;
* 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;
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;
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;
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;
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
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;
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