plpgsql does OUT parameters, as per my proposal a few weeks ago.
This commit is contained in:
parent
2af664e7ce
commit
fd97cf4df0
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.62 2005/03/13 09:36:30 neilc Exp $
|
||||
$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.63 2005/04/05 06:22:14 tgl Exp $
|
||||
-->
|
||||
|
||||
<chapter id="plpgsql">
|
||||
@ -83,7 +83,7 @@ $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.62 2005/03/13 09:36:30 neilc Ex
|
||||
that contains many statements for which execution plans might be
|
||||
required will only prepare and save those plans that are really
|
||||
used during the lifetime of the database connection. This can
|
||||
substantially reduce the total amount of time required to parse,
|
||||
substantially reduce the total amount of time required to parse
|
||||
and generate execution plans for the statements in a
|
||||
<application>PL/pgSQL</> function. A disadvantage is that errors
|
||||
in a specific expression or command may not be detected until that
|
||||
@ -215,6 +215,7 @@ $$ LANGUAGE plpgsql;
|
||||
<type>void</> if it has no useful return value.
|
||||
</para>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
<application>PL/pgSQL</> does not currently have full support for
|
||||
domain types: it treats a domain the same as the underlying scalar
|
||||
@ -223,6 +224,20 @@ $$ LANGUAGE plpgsql;
|
||||
it is a hazard if you declare a <application>PL/pgSQL</> function
|
||||
as returning a domain type.
|
||||
</para>
|
||||
</note>
|
||||
|
||||
<para>
|
||||
<application>PL/pgSQL</> functions can also be declared with output
|
||||
parameters in place of an explicit specification of the return type.
|
||||
This does not add any fundamental capability to the language, but
|
||||
it is often convenient, especially for returning multiple values.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Specific examples appear in
|
||||
<xref linkend="plpgsql-declaration-aliases"> and
|
||||
<xref linkend="plpgsql-statements-returning">.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
@ -631,12 +646,12 @@ DECLARE
|
||||
v_string ALIAS FOR $1;
|
||||
index ALIAS FOR $2;
|
||||
BEGIN
|
||||
-- some computations here
|
||||
-- some computations using v_string and index here
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE FUNCTION concat_selected_fields(in_t tablename) RETURNS text AS $$
|
||||
CREATE FUNCTION concat_selected_fields(in_t sometablename) RETURNS text AS $$
|
||||
BEGIN
|
||||
RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
|
||||
END;
|
||||
@ -644,6 +659,49 @@ $$ LANGUAGE plpgsql;
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When a <application>PL/pgSQL</application> function is declared
|
||||
with output parameters, the output parameters are given
|
||||
<literal>$<replaceable>n</replaceable></literal> names and optional
|
||||
aliases in just the same way as the normal input parameters. An
|
||||
output parameter is effectively a variable that starts out NULL;
|
||||
it should be assigned to during the execution of the function.
|
||||
The final value of the parameter is what is returned. For instance,
|
||||
the sales-tax example could also be done this way:
|
||||
|
||||
<programlisting>
|
||||
CREATE FUNCTION sales_tax(subtotal real, OUT tax real) AS $$
|
||||
BEGIN
|
||||
tax := subtotal * 0.06;
|
||||
RETURN;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
</programlisting>
|
||||
|
||||
Notice that we omitted <literal>RETURNS real</> — we could have
|
||||
included it, but it would be redundant.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Output parameters are most useful when returning multiple values.
|
||||
A trivial example is:
|
||||
|
||||
<programlisting>
|
||||
CREATE FUNCTION sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
|
||||
BEGIN
|
||||
sum := x + y;
|
||||
prod := x * y;
|
||||
RETURN;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
</programlisting>
|
||||
|
||||
As discussed in <xref linkend="xfunc-output-parameters">, this
|
||||
effectively creates an anonymous record type for the function's
|
||||
results. If a <literal>RETURNS</> clause is given, it must say
|
||||
<literal>RETURNS record</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When the return type of a <application>PL/pgSQL</application>
|
||||
function is declared as a polymorphic type (<type>anyelement</type>
|
||||
@ -658,6 +716,7 @@ $$ LANGUAGE plpgsql;
|
||||
though that is not required. <literal>$0</literal> can also be
|
||||
given an alias. For example, this function works on any data type
|
||||
that has a <literal>+</> operator:
|
||||
|
||||
<programlisting>
|
||||
CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
|
||||
RETURNS anyelement AS $$
|
||||
@ -668,6 +727,24 @@ BEGIN
|
||||
RETURN result;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The same effect can be had by declaring one or more output parameters as
|
||||
<type>anyelement</type> or <type>anyarray</type>. In this case the
|
||||
special <literal>$0</literal> parameter is not used; the output
|
||||
parameters themselves serve the same purpose. For example:
|
||||
|
||||
<programlisting>
|
||||
CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement,
|
||||
OUT sum anyelement)
|
||||
AS $$
|
||||
BEGIN
|
||||
sum := v1 + v2 + v3;
|
||||
RETURN;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
@ -756,18 +833,21 @@ user_id users.user_id%TYPE;
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Here is an example of using composite types:
|
||||
Here is an example of using composite types. <structname>table1</>
|
||||
and <structname>table2</> are existing tables having at least the
|
||||
mentioned fields:
|
||||
|
||||
<programlisting>
|
||||
CREATE FUNCTION merge_fields(t_row tablename) RETURNS text AS $$
|
||||
CREATE FUNCTION merge_fields(t_row table1) RETURNS text AS $$
|
||||
DECLARE
|
||||
t2_row table2name%ROWTYPE;
|
||||
t2_row table2%ROWTYPE;
|
||||
BEGIN
|
||||
SELECT * INTO t2_row FROM table2name WHERE ... ;
|
||||
SELECT * INTO t2_row FROM table2 WHERE ... ;
|
||||
RETURN t_row.f1 || t2_row.f3 || t_row.f5 || t2_row.f7;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
SELECT merge_fields(t.*) FROM tablename t WHERE ... ;
|
||||
SELECT merge_fields(t.*) FROM table1 t WHERE ... ;
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
@ -1411,6 +1491,12 @@ RETURN <replaceable>expression</replaceable>;
|
||||
as the <replaceable>expression</replaceable>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If you declared the function with output parameters, write just
|
||||
<command>RETURN</command> with no expression. The current values
|
||||
of the output parameter variables will be returned.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The return value of a function cannot be left undefined. If
|
||||
control reaches the end of the top-level block of the function
|
||||
@ -1441,8 +1527,30 @@ RETURN NEXT <replaceable>expression</replaceable>;
|
||||
commands, and then a final <command>RETURN</command> command
|
||||
with no argument is used to indicate that the function has
|
||||
finished executing. <command>RETURN NEXT</command> can be used
|
||||
with both scalar and composite data types; in the latter case, an
|
||||
entire <quote>table</quote> of results will be returned.
|
||||
with both scalar and composite data types; with a composite result
|
||||
type, an entire <quote>table</quote> of results will be returned.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<command>RETURN NEXT</command> does not actually return from the
|
||||
function — it simply saves away the value of the expression.
|
||||
Execution then continues with the next statement in
|
||||
the <application>PL/pgSQL</> function. As successive
|
||||
<command>RETURN NEXT</command> commands are executed, the result
|
||||
set is built up. A final <command>RETURN</command>, which should
|
||||
have no argument, causes control to exit the function.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If you declared the function with output parameters, write just
|
||||
<command>RETURN NEXT</command> with no expression. The current values
|
||||
of the output parameter variable(s) will be saved for eventual return.
|
||||
Note that you must declare the function as returning
|
||||
<literal>SETOF record</literal> when there are
|
||||
multiple output parameters, or
|
||||
<literal>SETOF <replaceable>sometype</></literal> when there is
|
||||
just one output parameter of type <replaceable>sometype</>, in
|
||||
order to create a set-returning function with output parameters.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -1457,16 +1565,6 @@ SELECT * FROM some_func();
|
||||
<literal>FROM</literal> clause.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<command>RETURN NEXT</command> does not actually return from the
|
||||
function; it simply saves away the value of the expression.
|
||||
Execution then continues with the next statement in
|
||||
the <application>PL/pgSQL</> function. As successive
|
||||
<command>RETURN NEXT</command> commands are executed, the result
|
||||
set is built up. A final <command>RETURN</command>, which should
|
||||
have no argument, causes control to exit the function.
|
||||
</para>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
The current implementation of <command>RETURN NEXT</command>
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.19 2005/03/31 22:46:16 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.20 2005/04/05 06:22:14 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -483,6 +483,108 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given the declared argument types and modes for a function,
|
||||
* replace any polymorphic types (ANYELEMENT/ANYARRAY) with correct data
|
||||
* types deduced from the input arguments. Returns TRUE if able to deduce
|
||||
* all types, FALSE if not. This is the same logic as
|
||||
* resolve_polymorphic_tupdesc, but with a different argument representation.
|
||||
*
|
||||
* argmodes may be NULL, in which case all arguments are assumed to be IN mode.
|
||||
*/
|
||||
bool
|
||||
resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
|
||||
Node *call_expr)
|
||||
{
|
||||
bool have_anyelement_result = false;
|
||||
bool have_anyarray_result = false;
|
||||
Oid anyelement_type = InvalidOid;
|
||||
Oid anyarray_type = InvalidOid;
|
||||
int inargno;
|
||||
int i;
|
||||
|
||||
/* First pass: resolve polymorphic inputs, check for outputs */
|
||||
inargno = 0;
|
||||
for (i = 0; i < numargs; i++)
|
||||
{
|
||||
char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
|
||||
|
||||
switch (argtypes[i])
|
||||
{
|
||||
case ANYELEMENTOID:
|
||||
if (argmode == PROARGMODE_OUT)
|
||||
have_anyelement_result = true;
|
||||
else
|
||||
{
|
||||
if (!OidIsValid(anyelement_type))
|
||||
{
|
||||
anyelement_type = get_call_expr_argtype(call_expr,
|
||||
inargno);
|
||||
if (!OidIsValid(anyelement_type))
|
||||
return false;
|
||||
}
|
||||
argtypes[i] = anyelement_type;
|
||||
}
|
||||
break;
|
||||
case ANYARRAYOID:
|
||||
if (argmode == PROARGMODE_OUT)
|
||||
have_anyarray_result = true;
|
||||
else
|
||||
{
|
||||
if (!OidIsValid(anyarray_type))
|
||||
{
|
||||
anyarray_type = get_call_expr_argtype(call_expr,
|
||||
inargno);
|
||||
if (!OidIsValid(anyarray_type))
|
||||
return false;
|
||||
}
|
||||
argtypes[i] = anyarray_type;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (argmode != PROARGMODE_OUT)
|
||||
inargno++;
|
||||
}
|
||||
|
||||
/* Done? */
|
||||
if (!have_anyelement_result && !have_anyarray_result)
|
||||
return true;
|
||||
|
||||
/* If no input polymorphics, parser messed up */
|
||||
if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type))
|
||||
return false;
|
||||
|
||||
/* If needed, deduce one polymorphic type from the other */
|
||||
if (have_anyelement_result && !OidIsValid(anyelement_type))
|
||||
anyelement_type = resolve_generic_type(ANYELEMENTOID,
|
||||
anyarray_type,
|
||||
ANYARRAYOID);
|
||||
if (have_anyarray_result && !OidIsValid(anyarray_type))
|
||||
anyarray_type = resolve_generic_type(ANYARRAYOID,
|
||||
anyelement_type,
|
||||
ANYELEMENTOID);
|
||||
|
||||
/* And finally replace the output column types as needed */
|
||||
for (i = 0; i < numargs; i++)
|
||||
{
|
||||
switch (argtypes[i])
|
||||
{
|
||||
case ANYELEMENTOID:
|
||||
argtypes[i] = anyelement_type;
|
||||
break;
|
||||
case ANYARRAYOID:
|
||||
argtypes[i] = anyarray_type;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_type_func_class
|
||||
* Given the type OID, obtain its TYPEFUNC classification.
|
||||
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/funcapi.h,v 1.16 2005/03/31 22:46:24 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/funcapi.h,v 1.17 2005/04/05 06:22:15 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -167,6 +167,10 @@ extern TypeFuncClass get_func_result_type(Oid functionId,
|
||||
Oid *resultTypeId,
|
||||
TupleDesc *resultTupleDesc);
|
||||
|
||||
extern bool resolve_polymorphic_argtypes(int numargs, Oid *argtypes,
|
||||
char *argmodes,
|
||||
Node *call_expr);
|
||||
|
||||
extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
|
||||
Datum proargmodes,
|
||||
Datum proargnames);
|
||||
|
@ -4,7 +4,7 @@
|
||||
* procedural language
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.66 2005/02/22 07:18:24 neilc Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.67 2005/04/05 06:22:16 tgl Exp $
|
||||
*
|
||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||
*
|
||||
@ -1052,28 +1052,41 @@ stmt_return : K_RETURN lno
|
||||
PLpgSQL_stmt_return *new;
|
||||
|
||||
new = palloc0(sizeof(PLpgSQL_stmt_return));
|
||||
new->cmd_type = PLPGSQL_STMT_RETURN;
|
||||
new->lineno = $2;
|
||||
new->expr = NULL;
|
||||
new->retrecno = -1;
|
||||
new->retrowno = -1;
|
||||
new->retvarno = -1;
|
||||
|
||||
if (plpgsql_curr_compile->fn_retset)
|
||||
{
|
||||
if (yylex() != ';')
|
||||
yyerror("RETURN cannot have a parameter in function returning set; use RETURN NEXT");
|
||||
}
|
||||
else if (plpgsql_curr_compile->out_param_varno >= 0)
|
||||
{
|
||||
if (yylex() != ';')
|
||||
yyerror("RETURN cannot have a parameter in function with OUT parameters");
|
||||
new->retvarno = plpgsql_curr_compile->out_param_varno;
|
||||
}
|
||||
else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
|
||||
{
|
||||
if (yylex() != ';')
|
||||
yyerror("function returning void cannot specify RETURN expression");
|
||||
}
|
||||
else if (plpgsql_curr_compile->fn_retistuple)
|
||||
{
|
||||
switch (yylex())
|
||||
{
|
||||
case K_NULL:
|
||||
/* we allow this to support RETURN NULL in triggers */
|
||||
break;
|
||||
|
||||
case T_ROW:
|
||||
new->retrowno = yylval.row->rowno;
|
||||
new->retvarno = yylval.row->rowno;
|
||||
break;
|
||||
|
||||
case T_RECORD:
|
||||
new->retrecno = yylval.rec->recno;
|
||||
new->retvarno = yylval.rec->recno;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1083,11 +1096,6 @@ stmt_return : K_RETURN lno
|
||||
if (yylex() != ';')
|
||||
yyerror("RETURN must specify a record or row variable in function returning tuple");
|
||||
}
|
||||
else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
|
||||
{
|
||||
if (yylex() != ';')
|
||||
yyerror("function returning void cannot specify RETURN expression");
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
@ -1098,9 +1106,6 @@ stmt_return : K_RETURN lno
|
||||
new->expr = plpgsql_read_expression(';', ";");
|
||||
}
|
||||
|
||||
new->cmd_type = PLPGSQL_STMT_RETURN;
|
||||
new->lineno = $2;
|
||||
|
||||
$$ = (PLpgSQL_stmt *)new;
|
||||
}
|
||||
;
|
||||
@ -1115,18 +1120,31 @@ stmt_return_next: K_RETURN_NEXT lno
|
||||
new = palloc0(sizeof(PLpgSQL_stmt_return_next));
|
||||
new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
|
||||
new->lineno = $2;
|
||||
new->expr = NULL;
|
||||
new->retvarno = -1;
|
||||
|
||||
if (plpgsql_curr_compile->fn_retistuple)
|
||||
if (plpgsql_curr_compile->out_param_varno >= 0)
|
||||
{
|
||||
int tok = yylex();
|
||||
if (yylex() != ';')
|
||||
yyerror("RETURN NEXT cannot have a parameter in function with OUT parameters");
|
||||
new->retvarno = plpgsql_curr_compile->out_param_varno;
|
||||
}
|
||||
else if (plpgsql_curr_compile->fn_retistuple)
|
||||
{
|
||||
switch (yylex())
|
||||
{
|
||||
case T_ROW:
|
||||
new->retvarno = yylval.row->rowno;
|
||||
break;
|
||||
|
||||
if (tok == T_RECORD)
|
||||
new->rec = yylval.rec;
|
||||
else if (tok == T_ROW)
|
||||
new->row = yylval.row;
|
||||
else
|
||||
yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
|
||||
case T_RECORD:
|
||||
new->retvarno = yylval.rec->recno;
|
||||
break;
|
||||
|
||||
default:
|
||||
yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
|
||||
break;
|
||||
}
|
||||
if (yylex() != ';')
|
||||
yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
* procedural language
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.85 2005/03/29 00:17:23 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.86 2005/04/05 06:22:16 tgl Exp $
|
||||
*
|
||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||
*
|
||||
@ -49,6 +49,7 @@
|
||||
#include "catalog/pg_class.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "funcapi.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "parser/gramparse.h"
|
||||
#include "parser/parse_type.h"
|
||||
@ -122,13 +123,20 @@ static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
|
||||
HeapTuple procTup,
|
||||
PLpgSQL_func_hashkey *hashkey,
|
||||
bool forValidator);
|
||||
static char **fetchArgNames(HeapTuple procTup, int nargs);
|
||||
static PLpgSQL_row *build_row_var(Oid classOid);
|
||||
static int fetchArgInfo(HeapTuple procTup,
|
||||
Oid **p_argtypes, char ***p_argnames,
|
||||
char **p_argmodes);
|
||||
static PLpgSQL_row *build_row_from_class(Oid classOid);
|
||||
static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars);
|
||||
static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
|
||||
static void compute_function_hashkey(FunctionCallInfo fcinfo,
|
||||
Form_pg_proc procStruct,
|
||||
PLpgSQL_func_hashkey *hashkey,
|
||||
bool forValidator);
|
||||
static void plpgsql_resolve_polymorphic_argtypes(int numargs,
|
||||
Oid *argtypes, char *argmodes,
|
||||
Node *call_expr, bool forValidator,
|
||||
const char *proname);
|
||||
static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key);
|
||||
static void plpgsql_HashTableInsert(PLpgSQL_function *function,
|
||||
PLpgSQL_func_hashkey *func_key);
|
||||
@ -259,11 +267,17 @@ do_compile(FunctionCallInfo fcinfo,
|
||||
PLpgSQL_variable *var;
|
||||
PLpgSQL_rec *rec;
|
||||
int i;
|
||||
int arg_varnos[FUNC_MAX_ARGS];
|
||||
ErrorContextCallback plerrcontext;
|
||||
int parse_rc;
|
||||
Oid rettypeid;
|
||||
int numargs;
|
||||
int num_in_args;
|
||||
int num_out_args;
|
||||
Oid *argtypes;
|
||||
char **argnames;
|
||||
char *argmodes;
|
||||
int *in_arg_varnos = NULL;
|
||||
PLpgSQL_variable **out_arg_variables;
|
||||
MemoryContext func_cxt;
|
||||
|
||||
/*
|
||||
@ -330,10 +344,110 @@ do_compile(FunctionCallInfo fcinfo,
|
||||
function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
|
||||
function->fn_functype = functype;
|
||||
function->fn_cxt = func_cxt;
|
||||
function->out_param_varno = -1; /* set up for no OUT param */
|
||||
|
||||
switch (functype)
|
||||
{
|
||||
case T_FUNCTION:
|
||||
/*
|
||||
* Fetch info about the procedure's parameters. Allocations
|
||||
* aren't needed permanently, so make them in tmp cxt.
|
||||
*
|
||||
* We also need to resolve any polymorphic input or output
|
||||
* argument types. In validation mode we won't be able to,
|
||||
* so we arbitrarily assume we are dealing with integers.
|
||||
*/
|
||||
MemoryContextSwitchTo(compile_tmp_cxt);
|
||||
|
||||
numargs = fetchArgInfo(procTup, &argtypes, &argnames, &argmodes);
|
||||
|
||||
plpgsql_resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
|
||||
fcinfo->flinfo->fn_expr,
|
||||
forValidator,
|
||||
plpgsql_error_funcname);
|
||||
|
||||
in_arg_varnos = (int *) palloc(numargs * sizeof(int));
|
||||
out_arg_variables = (PLpgSQL_variable **) palloc(numargs * sizeof(PLpgSQL_variable *));
|
||||
|
||||
MemoryContextSwitchTo(func_cxt);
|
||||
|
||||
/*
|
||||
* Create the variables for the procedure's parameters.
|
||||
*/
|
||||
num_in_args = num_out_args = 0;
|
||||
for (i = 0; i < numargs; i++)
|
||||
{
|
||||
char buf[32];
|
||||
Oid argtypeid = argtypes[i];
|
||||
char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
|
||||
PLpgSQL_type *argdtype;
|
||||
PLpgSQL_variable *argvariable;
|
||||
int argitemtype;
|
||||
|
||||
/* Create $n name for variable */
|
||||
snprintf(buf, sizeof(buf), "$%d", i + 1);
|
||||
|
||||
/* Create datatype info */
|
||||
argdtype = plpgsql_build_datatype(argtypeid, -1);
|
||||
|
||||
/* Disallow pseudotype argument */
|
||||
/* (note we already replaced ANYARRAY/ANYELEMENT) */
|
||||
/* (build_variable would do this, but wrong message) */
|
||||
if (argdtype->ttype != PLPGSQL_TTYPE_SCALAR &&
|
||||
argdtype->ttype != PLPGSQL_TTYPE_ROW)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("plpgsql functions cannot take type %s",
|
||||
format_type_be(argtypeid))));
|
||||
|
||||
/* Build variable and add to datum list */
|
||||
argvariable = plpgsql_build_variable(buf, 0,
|
||||
argdtype, false);
|
||||
|
||||
if (argvariable->dtype == PLPGSQL_DTYPE_VAR)
|
||||
{
|
||||
argitemtype = PLPGSQL_NSTYPE_VAR;
|
||||
/* input argument vars are forced to be CONSTANT */
|
||||
if (argmode == PROARGMODE_IN)
|
||||
((PLpgSQL_var *) argvariable)->isconst = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(argvariable->dtype == PLPGSQL_DTYPE_ROW);
|
||||
argitemtype = PLPGSQL_NSTYPE_ROW;
|
||||
}
|
||||
|
||||
/* Remember arguments in appropriate arrays */
|
||||
if (argmode == PROARGMODE_IN || argmode == PROARGMODE_INOUT)
|
||||
in_arg_varnos[num_in_args++] = argvariable->dno;
|
||||
if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_INOUT)
|
||||
out_arg_variables[num_out_args++] = argvariable;
|
||||
|
||||
/* Add to namespace under the $n name */
|
||||
plpgsql_ns_additem(argitemtype, argvariable->dno, buf);
|
||||
|
||||
/* If there's a name for the argument, make an alias */
|
||||
if (argnames && argnames[i][0] != '\0')
|
||||
plpgsql_ns_additem(argitemtype, argvariable->dno,
|
||||
argnames[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
if (num_out_args == 1)
|
||||
function->out_param_varno = out_arg_variables[0]->dno;
|
||||
else if (num_out_args > 1)
|
||||
{
|
||||
PLpgSQL_row *row = build_row_from_vars(out_arg_variables,
|
||||
num_out_args);
|
||||
|
||||
plpgsql_adddatum((PLpgSQL_datum *) row);
|
||||
function->out_param_varno = row->rowno;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for a polymorphic returntype. If found, use the
|
||||
* actual returntype type from the caller's FuncExpr node, if
|
||||
@ -355,13 +469,15 @@ do_compile(FunctionCallInfo fcinfo,
|
||||
rettypeid = INT4OID;
|
||||
}
|
||||
else
|
||||
{
|
||||
rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
|
||||
if (!OidIsValid(rettypeid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("could not determine actual return type "
|
||||
"for polymorphic function \"%s\"",
|
||||
plpgsql_error_funcname)));
|
||||
if (!OidIsValid(rettypeid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("could not determine actual return type "
|
||||
"for polymorphic function \"%s\"",
|
||||
plpgsql_error_funcname)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -410,10 +526,12 @@ do_compile(FunctionCallInfo fcinfo,
|
||||
|
||||
/*
|
||||
* install $0 reference, but only for polymorphic return
|
||||
* types
|
||||
* types, and not when the return is specified through an
|
||||
* output parameter.
|
||||
*/
|
||||
if (procStruct->prorettype == ANYARRAYOID ||
|
||||
procStruct->prorettype == ANYELEMENTOID)
|
||||
if ((procStruct->prorettype == ANYARRAYOID ||
|
||||
procStruct->prorettype == ANYELEMENTOID) &&
|
||||
num_out_args == 0)
|
||||
{
|
||||
(void) plpgsql_build_variable("$0", 0,
|
||||
build_datatype(typeTup, -1),
|
||||
@ -421,72 +539,6 @@ do_compile(FunctionCallInfo fcinfo,
|
||||
}
|
||||
}
|
||||
ReleaseSysCache(typeTup);
|
||||
|
||||
/*
|
||||
* Create the variables for the procedure's
|
||||
* parameters. Allocations aren't needed permanently, so
|
||||
* make them in tmp cxt.
|
||||
*/
|
||||
MemoryContextSwitchTo(compile_tmp_cxt);
|
||||
argnames = fetchArgNames(procTup, procStruct->pronargs);
|
||||
MemoryContextSwitchTo(func_cxt);
|
||||
|
||||
for (i = 0; i < procStruct->pronargs; i++)
|
||||
{
|
||||
char buf[32];
|
||||
Oid argtypeid;
|
||||
PLpgSQL_type *argdtype;
|
||||
PLpgSQL_variable *argvariable;
|
||||
int argitemtype;
|
||||
|
||||
/* Create $n name for variable */
|
||||
snprintf(buf, sizeof(buf), "$%d", i + 1);
|
||||
|
||||
/*
|
||||
* Since we already did the replacement of polymorphic
|
||||
* argument types by actual argument types while computing
|
||||
* the hashkey, we can just use those results.
|
||||
*/
|
||||
argtypeid = hashkey->argtypes[i];
|
||||
argdtype = plpgsql_build_datatype(argtypeid, -1);
|
||||
|
||||
/* Disallow pseudotype argument */
|
||||
/* (note we already replaced ANYARRAY/ANYELEMENT) */
|
||||
/* (build_variable would do this, but wrong message) */
|
||||
if (argdtype->ttype != PLPGSQL_TTYPE_SCALAR &&
|
||||
argdtype->ttype != PLPGSQL_TTYPE_ROW)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("plpgsql functions cannot take type %s",
|
||||
format_type_be(argtypeid))));
|
||||
|
||||
/* Build variable and add to datum list */
|
||||
argvariable = plpgsql_build_variable(buf, 0,
|
||||
argdtype, false);
|
||||
|
||||
if (argvariable->dtype == PLPGSQL_DTYPE_VAR)
|
||||
{
|
||||
/* argument vars are forced to be CONSTANT (why?) */
|
||||
((PLpgSQL_var *) argvariable)->isconst = true;
|
||||
argitemtype = PLPGSQL_NSTYPE_VAR;
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(argvariable->dtype == PLPGSQL_DTYPE_ROW);
|
||||
argitemtype = PLPGSQL_NSTYPE_ROW;
|
||||
}
|
||||
|
||||
/* Remember datum number */
|
||||
arg_varnos[i] = argvariable->dno;
|
||||
|
||||
/* Add to namespace under the $n name */
|
||||
plpgsql_ns_additem(argitemtype, argvariable->dno, buf);
|
||||
|
||||
/* If there's a name for the argument, make an alias */
|
||||
if (argnames)
|
||||
plpgsql_ns_additem(argitemtype, argvariable->dno,
|
||||
argnames[i]);
|
||||
}
|
||||
break;
|
||||
|
||||
case T_TRIGGER:
|
||||
@ -598,7 +650,7 @@ do_compile(FunctionCallInfo fcinfo,
|
||||
*/
|
||||
function->fn_nargs = procStruct->pronargs;
|
||||
for (i = 0; i < function->fn_nargs; i++)
|
||||
function->fn_argvarnos[i] = arg_varnos[i];
|
||||
function->fn_argvarnos[i] = in_arg_varnos[i];
|
||||
function->ndatums = plpgsql_nDatums;
|
||||
function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
|
||||
for (i = 0; i < plpgsql_nDatums; i++)
|
||||
@ -660,40 +712,96 @@ plpgsql_compile_error_callback(void *arg)
|
||||
|
||||
|
||||
/*
|
||||
* Fetch the argument names, if any, from the proargnames field of the
|
||||
* pg_proc tuple. Results are palloc'd.
|
||||
* Fetch info about the argument types, names, and IN/OUT modes from the
|
||||
* pg_proc tuple. Return value is the number of arguments.
|
||||
* Other results are palloc'd.
|
||||
*/
|
||||
static char **
|
||||
fetchArgNames(HeapTuple procTup, int nargs)
|
||||
static int
|
||||
fetchArgInfo(HeapTuple procTup, Oid **p_argtypes, char ***p_argnames,
|
||||
char **p_argmodes)
|
||||
{
|
||||
Datum argnamesDatum;
|
||||
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
|
||||
Datum proallargtypes;
|
||||
Datum proargmodes;
|
||||
Datum proargnames;
|
||||
bool isNull;
|
||||
ArrayType *arr;
|
||||
int numargs;
|
||||
Datum *elems;
|
||||
int nelems;
|
||||
char **result;
|
||||
int i;
|
||||
|
||||
if (nargs == 0)
|
||||
return NULL;
|
||||
/* First discover the total number of parameters and get their types */
|
||||
proallargtypes = SysCacheGetAttr(PROCOID, procTup,
|
||||
Anum_pg_proc_proallargtypes,
|
||||
&isNull);
|
||||
if (!isNull)
|
||||
{
|
||||
/*
|
||||
* We expect the arrays to be 1-D arrays of the right types; verify
|
||||
* that. For the OID and char arrays, we don't need to use
|
||||
* deconstruct_array() since the array data is just going to look like
|
||||
* a C array of values.
|
||||
*/
|
||||
arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */
|
||||
numargs = ARR_DIMS(arr)[0];
|
||||
if (ARR_NDIM(arr) != 1 ||
|
||||
numargs < 0 ||
|
||||
ARR_ELEMTYPE(arr) != OIDOID)
|
||||
elog(ERROR, "proallargtypes is not a 1-D Oid array");
|
||||
Assert(numargs >= procStruct->pronargs);
|
||||
*p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
|
||||
memcpy(*p_argtypes, ARR_DATA_PTR(arr),
|
||||
numargs * sizeof(Oid));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If no proallargtypes, use proargtypes */
|
||||
numargs = procStruct->proargtypes.dim1;
|
||||
Assert(numargs == procStruct->pronargs);
|
||||
*p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
|
||||
memcpy(*p_argtypes, procStruct->proargtypes.values,
|
||||
numargs * sizeof(Oid));
|
||||
}
|
||||
|
||||
argnamesDatum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames,
|
||||
&isNull);
|
||||
/* Get argument names, if available */
|
||||
proargnames = SysCacheGetAttr(PROCOID, procTup,
|
||||
Anum_pg_proc_proargnames,
|
||||
&isNull);
|
||||
if (isNull)
|
||||
return NULL;
|
||||
*p_argnames = NULL;
|
||||
else
|
||||
{
|
||||
deconstruct_array(DatumGetArrayTypeP(proargnames),
|
||||
TEXTOID, -1, false, 'i',
|
||||
&elems, &nelems);
|
||||
if (nelems != numargs) /* should not happen */
|
||||
elog(ERROR, "proargnames must have the same number of elements as the function has arguments");
|
||||
*p_argnames = (char **) palloc(sizeof(char *) * numargs);
|
||||
for (i = 0; i < numargs; i++)
|
||||
(*p_argnames)[i] = DatumGetCString(DirectFunctionCall1(textout,
|
||||
elems[i]));
|
||||
}
|
||||
|
||||
deconstruct_array(DatumGetArrayTypeP(argnamesDatum),
|
||||
TEXTOID, -1, false, 'i',
|
||||
&elems, &nelems);
|
||||
/* Get argument modes, if available */
|
||||
proargmodes = SysCacheGetAttr(PROCOID, procTup,
|
||||
Anum_pg_proc_proargmodes,
|
||||
&isNull);
|
||||
if (isNull)
|
||||
*p_argmodes = NULL;
|
||||
else
|
||||
{
|
||||
arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */
|
||||
if (ARR_NDIM(arr) != 1 ||
|
||||
ARR_DIMS(arr)[0] != numargs ||
|
||||
ARR_ELEMTYPE(arr) != CHAROID)
|
||||
elog(ERROR, "proargmodes is not a 1-D char array");
|
||||
*p_argmodes = (char *) palloc(numargs * sizeof(char));
|
||||
memcpy(*p_argmodes, ARR_DATA_PTR(arr),
|
||||
numargs * sizeof(char));
|
||||
}
|
||||
|
||||
if (nelems != nargs) /* should not happen */
|
||||
elog(ERROR, "proargnames must have the same number of elements as the function has arguments");
|
||||
|
||||
result = (char **) palloc(sizeof(char *) * nargs);
|
||||
|
||||
for (i = 0; i < nargs; i++)
|
||||
result[i] = DatumGetCString(DirectFunctionCall1(textout, elems[i]));
|
||||
|
||||
return result;
|
||||
return numargs;
|
||||
}
|
||||
|
||||
|
||||
@ -1449,7 +1557,7 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
|
||||
/* Composite type -- build a row variable */
|
||||
PLpgSQL_row *row;
|
||||
|
||||
row = build_row_var(dtype->typrelid);
|
||||
row = build_row_from_class(dtype->typrelid);
|
||||
|
||||
row->dtype = PLPGSQL_DTYPE_ROW;
|
||||
row->refname = pstrdup(refname);
|
||||
@ -1504,7 +1612,7 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
|
||||
* Build a row-variable data structure given the pg_class OID.
|
||||
*/
|
||||
static PLpgSQL_row *
|
||||
build_row_var(Oid classOid)
|
||||
build_row_from_class(Oid classOid)
|
||||
{
|
||||
PLpgSQL_row *row;
|
||||
Relation rel;
|
||||
@ -1589,6 +1697,62 @@ build_row_var(Oid classOid)
|
||||
return row;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build a row-variable data structure given the component variables.
|
||||
*/
|
||||
static PLpgSQL_row *
|
||||
build_row_from_vars(PLpgSQL_variable **vars, int numvars)
|
||||
{
|
||||
PLpgSQL_row *row;
|
||||
int i;
|
||||
|
||||
row = palloc0(sizeof(PLpgSQL_row));
|
||||
row->dtype = PLPGSQL_DTYPE_ROW;
|
||||
row->rowtupdesc = CreateTemplateTupleDesc(numvars, false);
|
||||
row->nfields = numvars;
|
||||
row->fieldnames = palloc(numvars * sizeof(char *));
|
||||
row->varnos = palloc(numvars * sizeof(int));
|
||||
|
||||
for (i = 0; i < numvars; i++)
|
||||
{
|
||||
PLpgSQL_variable *var = vars[i];
|
||||
Oid typoid = RECORDOID;
|
||||
int32 typmod = -1;
|
||||
|
||||
switch (var->dtype)
|
||||
{
|
||||
case PLPGSQL_DTYPE_VAR:
|
||||
typoid = ((PLpgSQL_var *) var)->datatype->typoid;
|
||||
typmod = ((PLpgSQL_var *) var)->datatype->atttypmod;
|
||||
break;
|
||||
|
||||
case PLPGSQL_DTYPE_REC:
|
||||
break;
|
||||
|
||||
case PLPGSQL_DTYPE_ROW:
|
||||
if (((PLpgSQL_row *) var)->rowtupdesc)
|
||||
{
|
||||
typoid = ((PLpgSQL_row *) var)->rowtupdesc->tdtypeid;
|
||||
typmod = ((PLpgSQL_row *) var)->rowtupdesc->tdtypmod;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized dtype: %d", var->dtype);
|
||||
}
|
||||
|
||||
row->fieldnames[i] = var->refname;
|
||||
row->varnos[i] = var->dno;
|
||||
|
||||
TupleDescInitEntry(row->rowtupdesc, i+1,
|
||||
var->refname,
|
||||
typoid, typmod,
|
||||
0);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
/* ----------
|
||||
* plpgsql_parse_datatype Scanner found something that should
|
||||
@ -1820,8 +1984,6 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
|
||||
PLpgSQL_func_hashkey *hashkey,
|
||||
bool forValidator)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Make sure any unused bytes of the struct are zero */
|
||||
MemSet(hashkey, 0, sizeof(PLpgSQL_func_hashkey));
|
||||
|
||||
@ -1840,42 +2002,64 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
|
||||
hashkey->trigrelOid = RelationGetRelid(trigdata->tg_relation);
|
||||
}
|
||||
|
||||
/* get the argument types */
|
||||
for (i = 0; i < procStruct->pronargs; i++)
|
||||
if (procStruct->pronargs > 0)
|
||||
{
|
||||
Oid argtypeid = procStruct->proargtypes.values[i];
|
||||
/* get the argument types */
|
||||
memcpy(hashkey->argtypes, procStruct->proargtypes.values,
|
||||
procStruct->pronargs * sizeof(Oid));
|
||||
|
||||
/*
|
||||
* Check for polymorphic arguments. If found, use the actual
|
||||
* parameter type from the caller's FuncExpr node, if we have one.
|
||||
* (In validation mode we arbitrarily assume we are dealing with
|
||||
* integers. This lets us build a valid, if possibly useless,
|
||||
* function hashtable entry.)
|
||||
*
|
||||
* We can support arguments of type ANY the same way as normal
|
||||
* polymorphic arguments.
|
||||
*/
|
||||
if (argtypeid == ANYARRAYOID || argtypeid == ANYELEMENTOID ||
|
||||
argtypeid == ANYOID)
|
||||
/* resolve any polymorphic argument types */
|
||||
plpgsql_resolve_polymorphic_argtypes(procStruct->pronargs,
|
||||
hashkey->argtypes,
|
||||
NULL,
|
||||
fcinfo->flinfo->fn_expr,
|
||||
forValidator,
|
||||
NameStr(procStruct->proname));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the same as the standard resolve_polymorphic_argtypes() function,
|
||||
* but with a special case for validation: assume that polymorphic arguments
|
||||
* are integer or integer-array. Also, we go ahead and report the error
|
||||
* if we can't resolve the types.
|
||||
*/
|
||||
static void
|
||||
plpgsql_resolve_polymorphic_argtypes(int numargs,
|
||||
Oid *argtypes, char *argmodes,
|
||||
Node *call_expr, bool forValidator,
|
||||
const char *proname)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!forValidator)
|
||||
{
|
||||
/* normal case, pass to standard routine */
|
||||
if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
|
||||
call_expr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("could not determine actual argument "
|
||||
"type for polymorphic function \"%s\"",
|
||||
proname)));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* special validation case */
|
||||
for (i = 0; i < numargs; i++)
|
||||
{
|
||||
if (forValidator)
|
||||
switch (argtypes[i])
|
||||
{
|
||||
if (argtypeid == ANYARRAYOID)
|
||||
argtypeid = INT4ARRAYOID;
|
||||
else
|
||||
argtypeid = INT4OID;
|
||||
case ANYELEMENTOID:
|
||||
argtypes[i] = INT4OID;
|
||||
break;
|
||||
case ANYARRAYOID:
|
||||
argtypes[i] = INT4ARRAYOID;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
else
|
||||
argtypeid = get_fn_expr_argtype(fcinfo->flinfo, i);
|
||||
if (!OidIsValid(argtypeid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("could not determine actual argument "
|
||||
"type for polymorphic function \"%s\"",
|
||||
NameStr(procStruct->proname))));
|
||||
}
|
||||
|
||||
hashkey->argtypes[i] = argtypeid;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
* procedural language
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.133 2005/03/25 01:45:42 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.134 2005/04/05 06:22:16 tgl Exp $
|
||||
*
|
||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||
*
|
||||
@ -75,8 +75,6 @@ static PLpgSQL_expr *active_simple_exprs = NULL;
|
||||
************************************************************/
|
||||
static void plpgsql_exec_error_callback(void *arg);
|
||||
static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
|
||||
static PLpgSQL_var *copy_var(PLpgSQL_var *var);
|
||||
static PLpgSQL_rec *copy_rec(PLpgSQL_rec *rec);
|
||||
|
||||
static int exec_stmt_block(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_stmt_block *block);
|
||||
@ -212,11 +210,11 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
|
||||
* Make local execution copies of all the datums
|
||||
*/
|
||||
estate.err_text = gettext_noop("during initialization of execution state");
|
||||
for (i = 0; i < func->ndatums; i++)
|
||||
for (i = 0; i < estate.ndatums; i++)
|
||||
estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
|
||||
|
||||
/*
|
||||
* Store the actual call argument values into the variables
|
||||
* Store the actual call argument values into the appropriate variables
|
||||
*/
|
||||
estate.err_text = gettext_noop("while storing call arguments into local variables");
|
||||
for (i = 0; i < func->fn_nargs; i++)
|
||||
@ -272,36 +270,6 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the other variables to NULL values for now. The default
|
||||
* values are set when the blocks are entered.
|
||||
*/
|
||||
estate.err_text = gettext_noop("while initializing local variables to NULL");
|
||||
for (i = estate.found_varno; i < estate.ndatums; i++)
|
||||
{
|
||||
switch (estate.datums[i]->dtype)
|
||||
{
|
||||
case PLPGSQL_DTYPE_VAR:
|
||||
{
|
||||
PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i];
|
||||
|
||||
var->value = 0;
|
||||
var->isnull = true;
|
||||
var->freeval = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case PLPGSQL_DTYPE_ROW:
|
||||
case PLPGSQL_DTYPE_REC:
|
||||
case PLPGSQL_DTYPE_RECFIELD:
|
||||
case PLPGSQL_DTYPE_ARRAYELEM:
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the magic variable FOUND to false
|
||||
*/
|
||||
@ -445,7 +413,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
|
||||
* Make local execution copies of all the datums
|
||||
*/
|
||||
estate.err_text = gettext_noop("during initialization of execution state");
|
||||
for (i = 0; i < func->ndatums; i++)
|
||||
for (i = 0; i < estate.ndatums; i++)
|
||||
estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
|
||||
|
||||
/*
|
||||
@ -551,7 +519,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
|
||||
var->freeval = false;
|
||||
|
||||
/*
|
||||
* Store the actual call argument values into the special execution
|
||||
* Store the trigger argument values into the special execution
|
||||
* state variables
|
||||
*/
|
||||
estate.err_text = gettext_noop("while storing call arguments into local variables");
|
||||
@ -566,37 +534,6 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
|
||||
CStringGetDatum(trigdata->tg_trigger->tgargs[i]));
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the other variables to NULL values for now. The default
|
||||
* values are set when the blocks are entered.
|
||||
*/
|
||||
estate.err_text = gettext_noop("while initializing local variables to NULL");
|
||||
for (i = estate.found_varno; i < estate.ndatums; i++)
|
||||
{
|
||||
switch (estate.datums[i]->dtype)
|
||||
{
|
||||
case PLPGSQL_DTYPE_VAR:
|
||||
{
|
||||
PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i];
|
||||
|
||||
var->value = 0;
|
||||
var->isnull = true;
|
||||
var->freeval = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case PLPGSQL_DTYPE_ROW:
|
||||
case PLPGSQL_DTYPE_REC:
|
||||
case PLPGSQL_DTYPE_RECFIELD:
|
||||
case PLPGSQL_DTYPE_ARRAYELEM:
|
||||
case PLPGSQL_DTYPE_TRIGARG:
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the magic variable FOUND to false
|
||||
*/
|
||||
@ -710,67 +647,65 @@ plpgsql_exec_error_callback(void *arg)
|
||||
|
||||
|
||||
/* ----------
|
||||
* Support functions for copying local execution variables
|
||||
*
|
||||
* NB: this is not a generic copy operation because it assumes that any
|
||||
* pass-by-ref original values will live as long as the copy is needed.
|
||||
* Support function for initializing local execution variables
|
||||
* ----------
|
||||
*/
|
||||
static PLpgSQL_datum *
|
||||
copy_plpgsql_datum(PLpgSQL_datum *datum)
|
||||
{
|
||||
PLpgSQL_datum *result = NULL;
|
||||
PLpgSQL_datum *result;
|
||||
|
||||
switch (datum->dtype)
|
||||
{
|
||||
case PLPGSQL_DTYPE_VAR:
|
||||
result = (PLpgSQL_datum *) copy_var((PLpgSQL_var *) datum);
|
||||
break;
|
||||
{
|
||||
PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
|
||||
|
||||
memcpy(new, datum, sizeof(PLpgSQL_var));
|
||||
/* Ensure the value is null (possibly not needed?) */
|
||||
new->value = 0;
|
||||
new->isnull = true;
|
||||
new->freeval = false;
|
||||
|
||||
result = (PLpgSQL_datum *) new;
|
||||
}
|
||||
break;
|
||||
|
||||
case PLPGSQL_DTYPE_REC:
|
||||
result = (PLpgSQL_datum *) copy_rec((PLpgSQL_rec *) datum);
|
||||
break;
|
||||
{
|
||||
PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
|
||||
|
||||
memcpy(new, datum, sizeof(PLpgSQL_rec));
|
||||
/* Ensure the value is null (possibly not needed?) */
|
||||
new->tup = NULL;
|
||||
new->tupdesc = NULL;
|
||||
new->freetup = false;
|
||||
new->freetupdesc = false;
|
||||
|
||||
result = (PLpgSQL_datum *) new;
|
||||
}
|
||||
break;
|
||||
|
||||
case PLPGSQL_DTYPE_ROW:
|
||||
case PLPGSQL_DTYPE_RECFIELD:
|
||||
case PLPGSQL_DTYPE_ARRAYELEM:
|
||||
case PLPGSQL_DTYPE_TRIGARG:
|
||||
/*
|
||||
* These datum records are read-only at runtime, so no need
|
||||
* to copy them
|
||||
*/
|
||||
result = datum;
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
|
||||
result = NULL; /* keep compiler quiet */
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static PLpgSQL_var *
|
||||
copy_var(PLpgSQL_var *var)
|
||||
{
|
||||
PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
|
||||
|
||||
memcpy(new, var, sizeof(PLpgSQL_var));
|
||||
new->freeval = false;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
|
||||
static PLpgSQL_rec *
|
||||
copy_rec(PLpgSQL_rec *rec)
|
||||
{
|
||||
PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
|
||||
|
||||
memcpy(new, rec, sizeof(PLpgSQL_rec));
|
||||
new->tup = NULL;
|
||||
new->tupdesc = NULL;
|
||||
new->freetup = false;
|
||||
new->freetupdesc = false;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond)
|
||||
@ -1682,43 +1617,64 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
|
||||
if (estate->retisset)
|
||||
return PLPGSQL_RC_RETURN;
|
||||
|
||||
if (estate->retistuple)
|
||||
/* initialize for null result (possibly a tuple) */
|
||||
estate->retval = (Datum) 0;
|
||||
estate->rettupdesc = NULL;
|
||||
estate->retisnull = true;
|
||||
|
||||
if (stmt->retvarno >= 0)
|
||||
{
|
||||
/* initialize for null result tuple */
|
||||
estate->retval = (Datum) 0;
|
||||
estate->rettupdesc = NULL;
|
||||
estate->retisnull = true;
|
||||
PLpgSQL_datum *retvar = estate->datums[stmt->retvarno];
|
||||
|
||||
if (stmt->retrecno >= 0)
|
||||
switch (retvar->dtype)
|
||||
{
|
||||
PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->retrecno]);
|
||||
|
||||
if (HeapTupleIsValid(rec->tup))
|
||||
case PLPGSQL_DTYPE_VAR:
|
||||
{
|
||||
estate->retval = (Datum) rec->tup;
|
||||
estate->rettupdesc = rec->tupdesc;
|
||||
estate->retisnull = false;
|
||||
PLpgSQL_var *var = (PLpgSQL_var *) retvar;
|
||||
|
||||
estate->retval = var->value;
|
||||
estate->retisnull = var->isnull;
|
||||
estate->rettype = var->datatype->typoid;
|
||||
}
|
||||
return PLPGSQL_RC_RETURN;
|
||||
}
|
||||
break;
|
||||
|
||||
if (stmt->retrowno >= 0)
|
||||
{
|
||||
PLpgSQL_row *row = (PLpgSQL_row *) (estate->datums[stmt->retrowno]);
|
||||
|
||||
if (row->rowtupdesc) /* should always be true here */
|
||||
case PLPGSQL_DTYPE_REC:
|
||||
{
|
||||
PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
|
||||
|
||||
if (HeapTupleIsValid(rec->tup))
|
||||
{
|
||||
estate->retval = (Datum) rec->tup;
|
||||
estate->rettupdesc = rec->tupdesc;
|
||||
estate->retisnull = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PLPGSQL_DTYPE_ROW:
|
||||
{
|
||||
PLpgSQL_row *row = (PLpgSQL_row *) retvar;
|
||||
|
||||
Assert(row->rowtupdesc);
|
||||
estate->retval = (Datum) make_tuple_from_row(estate, row,
|
||||
row->rowtupdesc);
|
||||
if (estate->retval == (Datum) NULL) /* should not happen */
|
||||
row->rowtupdesc);
|
||||
if (estate->retval == (Datum) NULL) /* should not happen */
|
||||
elog(ERROR, "row not compatible with its own tupdesc");
|
||||
estate->rettupdesc = row->rowtupdesc;
|
||||
estate->retisnull = false;
|
||||
}
|
||||
return PLPGSQL_RC_RETURN;
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized dtype: %d", retvar->dtype);
|
||||
}
|
||||
|
||||
if (stmt->expr != NULL)
|
||||
return PLPGSQL_RC_RETURN;
|
||||
}
|
||||
|
||||
if (stmt->expr != NULL)
|
||||
{
|
||||
if (estate->retistuple)
|
||||
{
|
||||
exec_run_select(estate, stmt->expr, 1, NULL);
|
||||
if (estate->eval_processed > 0)
|
||||
@ -1728,24 +1684,23 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
|
||||
estate->retisnull = false;
|
||||
}
|
||||
}
|
||||
return PLPGSQL_RC_RETURN;
|
||||
else
|
||||
{
|
||||
/* Normal case for scalar results */
|
||||
estate->retval = exec_eval_expr(estate, stmt->expr,
|
||||
&(estate->retisnull),
|
||||
&(estate->rettype));
|
||||
}
|
||||
}
|
||||
|
||||
if (estate->fn_rettype == VOIDOID)
|
||||
{
|
||||
/* Special hack for function returning VOID */
|
||||
Assert(stmt->expr == NULL);
|
||||
Assert(stmt->retvarno < 0 && stmt->expr == NULL);
|
||||
estate->retval = (Datum) 0;
|
||||
estate->retisnull = false;
|
||||
estate->rettype = VOIDOID;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Normal case for scalar results */
|
||||
estate->retval = exec_eval_expr(estate, stmt->expr,
|
||||
&(estate->retisnull),
|
||||
&(estate->rettype));
|
||||
}
|
||||
|
||||
return PLPGSQL_RC_RETURN;
|
||||
}
|
||||
@ -1777,37 +1732,78 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
|
||||
tupdesc = estate->rettupdesc;
|
||||
natts = tupdesc->natts;
|
||||
|
||||
if (stmt->rec)
|
||||
if (stmt->retvarno >= 0)
|
||||
{
|
||||
PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||
PLpgSQL_datum *retvar = estate->datums[stmt->retvarno];
|
||||
|
||||
if (!HeapTupleIsValid(rec->tup))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("record \"%s\" is not assigned yet",
|
||||
rec->refname),
|
||||
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
||||
if (!compatible_tupdesc(tupdesc, rec->tupdesc))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("wrong record type supplied in RETURN NEXT")));
|
||||
tuple = rec->tup;
|
||||
}
|
||||
else if (stmt->row)
|
||||
{
|
||||
tuple = make_tuple_from_row(estate, stmt->row, tupdesc);
|
||||
if (tuple == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("wrong record type supplied in RETURN NEXT")));
|
||||
free_tuple = true;
|
||||
switch (retvar->dtype)
|
||||
{
|
||||
case PLPGSQL_DTYPE_VAR:
|
||||
{
|
||||
PLpgSQL_var *var = (PLpgSQL_var *) retvar;
|
||||
Datum retval = var->value;
|
||||
bool isNull = var->isnull;
|
||||
|
||||
if (natts != 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("wrong result type supplied in RETURN NEXT")));
|
||||
|
||||
/* coerce type if needed */
|
||||
retval = exec_simple_cast_value(retval,
|
||||
var->datatype->typoid,
|
||||
tupdesc->attrs[0]->atttypid,
|
||||
tupdesc->attrs[0]->atttypmod,
|
||||
&isNull);
|
||||
|
||||
tuple = heap_form_tuple(tupdesc, &retval, &isNull);
|
||||
|
||||
free_tuple = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case PLPGSQL_DTYPE_REC:
|
||||
{
|
||||
PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
|
||||
|
||||
if (!HeapTupleIsValid(rec->tup))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("record \"%s\" is not assigned yet",
|
||||
rec->refname),
|
||||
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
||||
if (!compatible_tupdesc(tupdesc, rec->tupdesc))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("wrong record type supplied in RETURN NEXT")));
|
||||
tuple = rec->tup;
|
||||
}
|
||||
break;
|
||||
|
||||
case PLPGSQL_DTYPE_ROW:
|
||||
{
|
||||
PLpgSQL_row *row = (PLpgSQL_row *) retvar;
|
||||
|
||||
tuple = make_tuple_from_row(estate, row, tupdesc);
|
||||
if (tuple == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("wrong record type supplied in RETURN NEXT")));
|
||||
free_tuple = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized dtype: %d", retvar->dtype);
|
||||
tuple = NULL; /* keep compiler quiet */
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (stmt->expr)
|
||||
{
|
||||
Datum retval;
|
||||
bool isNull;
|
||||
Oid rettype;
|
||||
char nullflag;
|
||||
|
||||
if (natts != 1)
|
||||
ereport(ERROR,
|
||||
@ -1826,9 +1822,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
|
||||
tupdesc->attrs[0]->atttypmod,
|
||||
&isNull);
|
||||
|
||||
nullflag = isNull ? 'n' : ' ';
|
||||
|
||||
tuple = heap_formtuple(tupdesc, &retval, &nullflag);
|
||||
tuple = heap_form_tuple(tupdesc, &retval, &isNull);
|
||||
|
||||
free_tuple = true;
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
* procedural language
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.39 2005/02/22 07:18:24 neilc Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.40 2005/04/05 06:22:16 tgl Exp $
|
||||
*
|
||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||
*
|
||||
@ -857,14 +857,12 @@ dump_return(PLpgSQL_stmt_return *stmt)
|
||||
{
|
||||
dump_ind();
|
||||
printf("RETURN ");
|
||||
if (stmt->retrecno >= 0)
|
||||
printf("record %d", stmt->retrecno);
|
||||
else if (stmt->retrowno >= 0)
|
||||
printf("row %d", stmt->retrowno);
|
||||
else if (stmt->expr == NULL)
|
||||
printf("NULL");
|
||||
else
|
||||
if (stmt->retvarno >= 0)
|
||||
printf("variable %d", stmt->retvarno);
|
||||
else if (stmt->expr != NULL)
|
||||
dump_expr(stmt->expr);
|
||||
else
|
||||
printf("NULL");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
@ -873,12 +871,12 @@ dump_return_next(PLpgSQL_stmt_return_next *stmt)
|
||||
{
|
||||
dump_ind();
|
||||
printf("RETURN NEXT ");
|
||||
if (stmt->rec != NULL)
|
||||
printf("target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
|
||||
else if (stmt->row != NULL)
|
||||
printf("target = %d %s\n", stmt->row->rowno, stmt->row->refname);
|
||||
if (stmt->retvarno >= 0)
|
||||
printf("variable %d", stmt->retvarno);
|
||||
else if (stmt->expr != NULL)
|
||||
dump_expr(stmt->expr);
|
||||
else
|
||||
printf("NULL");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
* procedural language
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.57 2005/02/22 07:18:24 neilc Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.58 2005/04/05 06:22:16 tgl Exp $
|
||||
*
|
||||
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||
*
|
||||
@ -491,17 +491,15 @@ typedef struct
|
||||
int cmd_type;
|
||||
int lineno;
|
||||
PLpgSQL_expr *expr;
|
||||
int retrecno;
|
||||
int retrowno;
|
||||
int retvarno;
|
||||
} PLpgSQL_stmt_return;
|
||||
|
||||
typedef struct
|
||||
{ /* RETURN NEXT statement */
|
||||
int cmd_type;
|
||||
int lineno;
|
||||
PLpgSQL_rec *rec;
|
||||
PLpgSQL_row *row;
|
||||
PLpgSQL_expr *expr;
|
||||
int retvarno;
|
||||
} PLpgSQL_stmt_return_next;
|
||||
|
||||
typedef struct
|
||||
@ -572,6 +570,7 @@ typedef struct PLpgSQL_function
|
||||
|
||||
int fn_nargs;
|
||||
int fn_argvarnos[FUNC_MAX_ARGS];
|
||||
int out_param_varno;
|
||||
int found_varno;
|
||||
int new_varno;
|
||||
int old_varno;
|
||||
|
@ -1738,6 +1738,125 @@ SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text);
|
||||
50 | 5 | xxx
|
||||
(1 row)
|
||||
|
||||
--
|
||||
-- Test handling of OUT parameters, including polymorphic cases
|
||||
--
|
||||
-- wrong way to do it:
|
||||
create function f1(in i int, out j int) returns int as $$
|
||||
begin
|
||||
return i+1;
|
||||
end$$ language plpgsql;
|
||||
ERROR: RETURN cannot have a parameter in function with OUT parameters at or near "i" at character 74
|
||||
LINE 3: return i+1;
|
||||
^
|
||||
create function f1(in i int, out j int) as $$
|
||||
begin
|
||||
j := i+1;
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
select f1(42);
|
||||
f1
|
||||
----
|
||||
43
|
||||
(1 row)
|
||||
|
||||
select * from f1(42);
|
||||
f1
|
||||
----
|
||||
43
|
||||
(1 row)
|
||||
|
||||
create or replace function f1(inout i int) as $$
|
||||
begin
|
||||
i := i+1;
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
select f1(42);
|
||||
f1
|
||||
----
|
||||
43
|
||||
(1 row)
|
||||
|
||||
select * from f1(42);
|
||||
f1
|
||||
----
|
||||
43
|
||||
(1 row)
|
||||
|
||||
drop function f1(int);
|
||||
create function f1(in i int, out j int) returns setof int as $$
|
||||
begin
|
||||
j := i+1;
|
||||
return next;
|
||||
j := i+2;
|
||||
return next;
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
select * from f1(42);
|
||||
f1
|
||||
----
|
||||
43
|
||||
44
|
||||
(2 rows)
|
||||
|
||||
drop function f1(int);
|
||||
create function f1(in i int, out j int, out k text) as $$
|
||||
begin
|
||||
j := i;
|
||||
j := j+1;
|
||||
k := 'foo';
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
select f1(42);
|
||||
f1
|
||||
----------
|
||||
(43,foo)
|
||||
(1 row)
|
||||
|
||||
select * from f1(42);
|
||||
j | k
|
||||
----+-----
|
||||
43 | foo
|
||||
(1 row)
|
||||
|
||||
drop function f1(int);
|
||||
create function f1(in i int, out j int, out k text) returns setof record as $$
|
||||
begin
|
||||
j := i+1;
|
||||
k := 'foo';
|
||||
return next;
|
||||
j := j+1;
|
||||
k := 'foot';
|
||||
return next;
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
select * from f1(42);
|
||||
j | k
|
||||
----+------
|
||||
43 | foo
|
||||
44 | foot
|
||||
(2 rows)
|
||||
|
||||
drop function f1(int);
|
||||
create function dup(in i anyelement, out j anyelement, out k anyarray) as $$
|
||||
begin
|
||||
j := i;
|
||||
k := array[j,j];
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
select * from dup(42);
|
||||
j | k
|
||||
----+---------
|
||||
42 | {42,42}
|
||||
(1 row)
|
||||
|
||||
select * from dup('foo'::text);
|
||||
j | k
|
||||
-----+-----------
|
||||
foo | {foo,foo}
|
||||
(1 row)
|
||||
|
||||
drop function dup(anyelement);
|
||||
--
|
||||
-- test PERFORM
|
||||
--
|
||||
|
@ -1560,6 +1560,89 @@ END;' language 'plpgsql';
|
||||
SELECT * FROM test_ret_rec_dyn(1500) AS (a int, b int, c int);
|
||||
SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text);
|
||||
|
||||
--
|
||||
-- Test handling of OUT parameters, including polymorphic cases
|
||||
--
|
||||
|
||||
-- wrong way to do it:
|
||||
create function f1(in i int, out j int) returns int as $$
|
||||
begin
|
||||
return i+1;
|
||||
end$$ language plpgsql;
|
||||
|
||||
create function f1(in i int, out j int) as $$
|
||||
begin
|
||||
j := i+1;
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
|
||||
select f1(42);
|
||||
select * from f1(42);
|
||||
|
||||
create or replace function f1(inout i int) as $$
|
||||
begin
|
||||
i := i+1;
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
|
||||
select f1(42);
|
||||
select * from f1(42);
|
||||
|
||||
drop function f1(int);
|
||||
|
||||
create function f1(in i int, out j int) returns setof int as $$
|
||||
begin
|
||||
j := i+1;
|
||||
return next;
|
||||
j := i+2;
|
||||
return next;
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
|
||||
select * from f1(42);
|
||||
|
||||
drop function f1(int);
|
||||
|
||||
create function f1(in i int, out j int, out k text) as $$
|
||||
begin
|
||||
j := i;
|
||||
j := j+1;
|
||||
k := 'foo';
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
|
||||
select f1(42);
|
||||
select * from f1(42);
|
||||
|
||||
drop function f1(int);
|
||||
|
||||
create function f1(in i int, out j int, out k text) returns setof record as $$
|
||||
begin
|
||||
j := i+1;
|
||||
k := 'foo';
|
||||
return next;
|
||||
j := j+1;
|
||||
k := 'foot';
|
||||
return next;
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
|
||||
select * from f1(42);
|
||||
|
||||
drop function f1(int);
|
||||
|
||||
create function dup(in i anyelement, out j anyelement, out k anyarray) as $$
|
||||
begin
|
||||
j := i;
|
||||
k := array[j,j];
|
||||
return;
|
||||
end$$ language plpgsql;
|
||||
|
||||
select * from dup(42);
|
||||
select * from dup('foo'::text);
|
||||
|
||||
drop function dup(anyelement);
|
||||
|
||||
--
|
||||
-- test PERFORM
|
||||
--
|
||||
@ -1917,4 +2000,4 @@ end;$$ language plpgsql;
|
||||
create function void_return_expr() returns void as $$
|
||||
begin
|
||||
return 5;
|
||||
end;$$ language plpgsql;
|
||||
end;$$ language plpgsql;
|
||||
|
Loading…
x
Reference in New Issue
Block a user