mirror of
https://github.com/postgres/postgres.git
synced 2025-07-12 21:01:52 +03:00
plpgsql does OUT parameters, as per my proposal a few weeks ago.
This commit is contained in:
@ -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">
|
<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
|
that contains many statements for which execution plans might be
|
||||||
required will only prepare and save those plans that are really
|
required will only prepare and save those plans that are really
|
||||||
used during the lifetime of the database connection. This can
|
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
|
and generate execution plans for the statements in a
|
||||||
<application>PL/pgSQL</> function. A disadvantage is that errors
|
<application>PL/pgSQL</> function. A disadvantage is that errors
|
||||||
in a specific expression or command may not be detected until that
|
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.
|
<type>void</> if it has no useful return value.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<note>
|
||||||
<para>
|
<para>
|
||||||
<application>PL/pgSQL</> does not currently have full support for
|
<application>PL/pgSQL</> does not currently have full support for
|
||||||
domain types: it treats a domain the same as the underlying scalar
|
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
|
it is a hazard if you declare a <application>PL/pgSQL</> function
|
||||||
as returning a domain type.
|
as returning a domain type.
|
||||||
</para>
|
</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>
|
</sect2>
|
||||||
</sect1>
|
</sect1>
|
||||||
|
|
||||||
@ -631,12 +646,12 @@ DECLARE
|
|||||||
v_string ALIAS FOR $1;
|
v_string ALIAS FOR $1;
|
||||||
index ALIAS FOR $2;
|
index ALIAS FOR $2;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- some computations here
|
-- some computations using v_string and index here
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ 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
|
BEGIN
|
||||||
RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
|
RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
|
||||||
END;
|
END;
|
||||||
@ -644,6 +659,49 @@ $$ LANGUAGE plpgsql;
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</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>
|
<para>
|
||||||
When the return type of a <application>PL/pgSQL</application>
|
When the return type of a <application>PL/pgSQL</application>
|
||||||
function is declared as a polymorphic type (<type>anyelement</type>
|
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
|
though that is not required. <literal>$0</literal> can also be
|
||||||
given an alias. For example, this function works on any data type
|
given an alias. For example, this function works on any data type
|
||||||
that has a <literal>+</> operator:
|
that has a <literal>+</> operator:
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
|
CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
|
||||||
RETURNS anyelement AS $$
|
RETURNS anyelement AS $$
|
||||||
@ -668,6 +727,24 @@ BEGIN
|
|||||||
RETURN result;
|
RETURN result;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ 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>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
@ -756,18 +833,21 @@ user_id users.user_id%TYPE;
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<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>
|
<programlisting>
|
||||||
CREATE FUNCTION merge_fields(t_row tablename) RETURNS text AS $$
|
CREATE FUNCTION merge_fields(t_row table1) RETURNS text AS $$
|
||||||
DECLARE
|
DECLARE
|
||||||
t2_row table2name%ROWTYPE;
|
t2_row table2%ROWTYPE;
|
||||||
BEGIN
|
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;
|
RETURN t_row.f1 || t2_row.f3 || t_row.f5 || t2_row.f7;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
SELECT merge_fields(t.*) FROM tablename t WHERE ... ;
|
SELECT merge_fields(t.*) FROM table1 t WHERE ... ;
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
@ -1411,6 +1491,12 @@ RETURN <replaceable>expression</replaceable>;
|
|||||||
as the <replaceable>expression</replaceable>.
|
as the <replaceable>expression</replaceable>.
|
||||||
</para>
|
</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>
|
<para>
|
||||||
The return value of a function cannot be left undefined. If
|
The return value of a function cannot be left undefined. If
|
||||||
control reaches the end of the top-level block of the function
|
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
|
commands, and then a final <command>RETURN</command> command
|
||||||
with no argument is used to indicate that the function has
|
with no argument is used to indicate that the function has
|
||||||
finished executing. <command>RETURN NEXT</command> can be used
|
finished executing. <command>RETURN NEXT</command> can be used
|
||||||
with both scalar and composite data types; in the latter case, an
|
with both scalar and composite data types; with a composite result
|
||||||
entire <quote>table</quote> of results will be returned.
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -1457,16 +1565,6 @@ SELECT * FROM some_func();
|
|||||||
<literal>FROM</literal> clause.
|
<literal>FROM</literal> clause.
|
||||||
</para>
|
</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>
|
<note>
|
||||||
<para>
|
<para>
|
||||||
The current implementation of <command>RETURN NEXT</command>
|
The current implementation of <command>RETURN NEXT</command>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
|
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
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
|
* get_type_func_class
|
||||||
* Given the type OID, obtain its TYPEFUNC classification.
|
* Given the type OID, obtain its TYPEFUNC classification.
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
|
* 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,
|
Oid *resultTypeId,
|
||||||
TupleDesc *resultTupleDesc);
|
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,
|
extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
|
||||||
Datum proargmodes,
|
Datum proargmodes,
|
||||||
Datum proargnames);
|
Datum proargnames);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -1052,28 +1052,41 @@ stmt_return : K_RETURN lno
|
|||||||
PLpgSQL_stmt_return *new;
|
PLpgSQL_stmt_return *new;
|
||||||
|
|
||||||
new = palloc0(sizeof(PLpgSQL_stmt_return));
|
new = palloc0(sizeof(PLpgSQL_stmt_return));
|
||||||
|
new->cmd_type = PLPGSQL_STMT_RETURN;
|
||||||
|
new->lineno = $2;
|
||||||
new->expr = NULL;
|
new->expr = NULL;
|
||||||
new->retrecno = -1;
|
new->retvarno = -1;
|
||||||
new->retrowno = -1;
|
|
||||||
|
|
||||||
if (plpgsql_curr_compile->fn_retset)
|
if (plpgsql_curr_compile->fn_retset)
|
||||||
{
|
{
|
||||||
if (yylex() != ';')
|
if (yylex() != ';')
|
||||||
yyerror("RETURN cannot have a parameter in function returning set; use RETURN NEXT");
|
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)
|
else if (plpgsql_curr_compile->fn_retistuple)
|
||||||
{
|
{
|
||||||
switch (yylex())
|
switch (yylex())
|
||||||
{
|
{
|
||||||
case K_NULL:
|
case K_NULL:
|
||||||
|
/* we allow this to support RETURN NULL in triggers */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_ROW:
|
case T_ROW:
|
||||||
new->retrowno = yylval.row->rowno;
|
new->retvarno = yylval.row->rowno;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_RECORD:
|
case T_RECORD:
|
||||||
new->retrecno = yylval.rec->recno;
|
new->retvarno = yylval.rec->recno;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -1083,11 +1096,6 @@ stmt_return : K_RETURN lno
|
|||||||
if (yylex() != ';')
|
if (yylex() != ';')
|
||||||
yyerror("RETURN must specify a record or row variable in function returning tuple");
|
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
|
else
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@ -1098,9 +1106,6 @@ stmt_return : K_RETURN lno
|
|||||||
new->expr = plpgsql_read_expression(';', ";");
|
new->expr = plpgsql_read_expression(';', ";");
|
||||||
}
|
}
|
||||||
|
|
||||||
new->cmd_type = PLPGSQL_STMT_RETURN;
|
|
||||||
new->lineno = $2;
|
|
||||||
|
|
||||||
$$ = (PLpgSQL_stmt *)new;
|
$$ = (PLpgSQL_stmt *)new;
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
@ -1115,18 +1120,31 @@ stmt_return_next: K_RETURN_NEXT lno
|
|||||||
new = palloc0(sizeof(PLpgSQL_stmt_return_next));
|
new = palloc0(sizeof(PLpgSQL_stmt_return_next));
|
||||||
new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
|
new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
|
||||||
new->lineno = $2;
|
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)
|
case T_RECORD:
|
||||||
new->rec = yylval.rec;
|
new->retvarno = yylval.rec->recno;
|
||||||
else if (tok == T_ROW)
|
break;
|
||||||
new->row = yylval.row;
|
|
||||||
else
|
|
||||||
yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (yylex() != ';')
|
if (yylex() != ';')
|
||||||
yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
|
yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -49,6 +49,7 @@
|
|||||||
#include "catalog/pg_class.h"
|
#include "catalog/pg_class.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
|
#include "funcapi.h"
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "parser/gramparse.h"
|
#include "parser/gramparse.h"
|
||||||
#include "parser/parse_type.h"
|
#include "parser/parse_type.h"
|
||||||
@ -122,13 +123,20 @@ static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
|
|||||||
HeapTuple procTup,
|
HeapTuple procTup,
|
||||||
PLpgSQL_func_hashkey *hashkey,
|
PLpgSQL_func_hashkey *hashkey,
|
||||||
bool forValidator);
|
bool forValidator);
|
||||||
static char **fetchArgNames(HeapTuple procTup, int nargs);
|
static int fetchArgInfo(HeapTuple procTup,
|
||||||
static PLpgSQL_row *build_row_var(Oid classOid);
|
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 PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
|
||||||
static void compute_function_hashkey(FunctionCallInfo fcinfo,
|
static void compute_function_hashkey(FunctionCallInfo fcinfo,
|
||||||
Form_pg_proc procStruct,
|
Form_pg_proc procStruct,
|
||||||
PLpgSQL_func_hashkey *hashkey,
|
PLpgSQL_func_hashkey *hashkey,
|
||||||
bool forValidator);
|
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 PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key);
|
||||||
static void plpgsql_HashTableInsert(PLpgSQL_function *function,
|
static void plpgsql_HashTableInsert(PLpgSQL_function *function,
|
||||||
PLpgSQL_func_hashkey *func_key);
|
PLpgSQL_func_hashkey *func_key);
|
||||||
@ -259,11 +267,17 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
PLpgSQL_variable *var;
|
PLpgSQL_variable *var;
|
||||||
PLpgSQL_rec *rec;
|
PLpgSQL_rec *rec;
|
||||||
int i;
|
int i;
|
||||||
int arg_varnos[FUNC_MAX_ARGS];
|
|
||||||
ErrorContextCallback plerrcontext;
|
ErrorContextCallback plerrcontext;
|
||||||
int parse_rc;
|
int parse_rc;
|
||||||
Oid rettypeid;
|
Oid rettypeid;
|
||||||
|
int numargs;
|
||||||
|
int num_in_args;
|
||||||
|
int num_out_args;
|
||||||
|
Oid *argtypes;
|
||||||
char **argnames;
|
char **argnames;
|
||||||
|
char *argmodes;
|
||||||
|
int *in_arg_varnos = NULL;
|
||||||
|
PLpgSQL_variable **out_arg_variables;
|
||||||
MemoryContext func_cxt;
|
MemoryContext func_cxt;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -330,10 +344,110 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
|
function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
|
||||||
function->fn_functype = functype;
|
function->fn_functype = functype;
|
||||||
function->fn_cxt = func_cxt;
|
function->fn_cxt = func_cxt;
|
||||||
|
function->out_param_varno = -1; /* set up for no OUT param */
|
||||||
|
|
||||||
switch (functype)
|
switch (functype)
|
||||||
{
|
{
|
||||||
case T_FUNCTION:
|
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
|
* Check for a polymorphic returntype. If found, use the
|
||||||
* actual returntype type from the caller's FuncExpr node, if
|
* actual returntype type from the caller's FuncExpr node, if
|
||||||
@ -355,13 +469,15 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
rettypeid = INT4OID;
|
rettypeid = INT4OID;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
|
rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
|
||||||
if (!OidIsValid(rettypeid))
|
if (!OidIsValid(rettypeid))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
errmsg("could not determine actual return type "
|
errmsg("could not determine actual return type "
|
||||||
"for polymorphic function \"%s\"",
|
"for polymorphic function \"%s\"",
|
||||||
plpgsql_error_funcname)));
|
plpgsql_error_funcname)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -410,10 +526,12 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* install $0 reference, but only for polymorphic return
|
* 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 ||
|
if ((procStruct->prorettype == ANYARRAYOID ||
|
||||||
procStruct->prorettype == ANYELEMENTOID)
|
procStruct->prorettype == ANYELEMENTOID) &&
|
||||||
|
num_out_args == 0)
|
||||||
{
|
{
|
||||||
(void) plpgsql_build_variable("$0", 0,
|
(void) plpgsql_build_variable("$0", 0,
|
||||||
build_datatype(typeTup, -1),
|
build_datatype(typeTup, -1),
|
||||||
@ -421,72 +539,6 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReleaseSysCache(typeTup);
|
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;
|
break;
|
||||||
|
|
||||||
case T_TRIGGER:
|
case T_TRIGGER:
|
||||||
@ -598,7 +650,7 @@ do_compile(FunctionCallInfo fcinfo,
|
|||||||
*/
|
*/
|
||||||
function->fn_nargs = procStruct->pronargs;
|
function->fn_nargs = procStruct->pronargs;
|
||||||
for (i = 0; i < function->fn_nargs; i++)
|
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->ndatums = plpgsql_nDatums;
|
||||||
function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
|
function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
|
||||||
for (i = 0; i < plpgsql_nDatums; i++)
|
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
|
* Fetch info about the argument types, names, and IN/OUT modes from the
|
||||||
* pg_proc tuple. Results are palloc'd.
|
* pg_proc tuple. Return value is the number of arguments.
|
||||||
|
* Other results are palloc'd.
|
||||||
*/
|
*/
|
||||||
static char **
|
static int
|
||||||
fetchArgNames(HeapTuple procTup, int nargs)
|
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;
|
bool isNull;
|
||||||
|
ArrayType *arr;
|
||||||
|
int numargs;
|
||||||
Datum *elems;
|
Datum *elems;
|
||||||
int nelems;
|
int nelems;
|
||||||
char **result;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (nargs == 0)
|
/* First discover the total number of parameters and get their types */
|
||||||
return NULL;
|
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,
|
/* Get argument names, if available */
|
||||||
&isNull);
|
proargnames = SysCacheGetAttr(PROCOID, procTup,
|
||||||
|
Anum_pg_proc_proargnames,
|
||||||
|
&isNull);
|
||||||
if (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),
|
/* Get argument modes, if available */
|
||||||
TEXTOID, -1, false, 'i',
|
proargmodes = SysCacheGetAttr(PROCOID, procTup,
|
||||||
&elems, &nelems);
|
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 */
|
return numargs;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1449,7 +1557,7 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
|
|||||||
/* Composite type -- build a row variable */
|
/* Composite type -- build a row variable */
|
||||||
PLpgSQL_row *row;
|
PLpgSQL_row *row;
|
||||||
|
|
||||||
row = build_row_var(dtype->typrelid);
|
row = build_row_from_class(dtype->typrelid);
|
||||||
|
|
||||||
row->dtype = PLPGSQL_DTYPE_ROW;
|
row->dtype = PLPGSQL_DTYPE_ROW;
|
||||||
row->refname = pstrdup(refname);
|
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.
|
* Build a row-variable data structure given the pg_class OID.
|
||||||
*/
|
*/
|
||||||
static PLpgSQL_row *
|
static PLpgSQL_row *
|
||||||
build_row_var(Oid classOid)
|
build_row_from_class(Oid classOid)
|
||||||
{
|
{
|
||||||
PLpgSQL_row *row;
|
PLpgSQL_row *row;
|
||||||
Relation rel;
|
Relation rel;
|
||||||
@ -1589,6 +1697,62 @@ build_row_var(Oid classOid)
|
|||||||
return row;
|
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
|
* plpgsql_parse_datatype Scanner found something that should
|
||||||
@ -1820,8 +1984,6 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
|
|||||||
PLpgSQL_func_hashkey *hashkey,
|
PLpgSQL_func_hashkey *hashkey,
|
||||||
bool forValidator)
|
bool forValidator)
|
||||||
{
|
{
|
||||||
int i;
|
|
||||||
|
|
||||||
/* Make sure any unused bytes of the struct are zero */
|
/* Make sure any unused bytes of the struct are zero */
|
||||||
MemSet(hashkey, 0, sizeof(PLpgSQL_func_hashkey));
|
MemSet(hashkey, 0, sizeof(PLpgSQL_func_hashkey));
|
||||||
|
|
||||||
@ -1840,42 +2002,64 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
|
|||||||
hashkey->trigrelOid = RelationGetRelid(trigdata->tg_relation);
|
hashkey->trigrelOid = RelationGetRelid(trigdata->tg_relation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get the argument types */
|
if (procStruct->pronargs > 0)
|
||||||
for (i = 0; i < procStruct->pronargs; i++)
|
|
||||||
{
|
{
|
||||||
Oid argtypeid = procStruct->proargtypes.values[i];
|
/* get the argument types */
|
||||||
|
memcpy(hashkey->argtypes, procStruct->proargtypes.values,
|
||||||
|
procStruct->pronargs * sizeof(Oid));
|
||||||
|
|
||||||
/*
|
/* resolve any polymorphic argument types */
|
||||||
* Check for polymorphic arguments. If found, use the actual
|
plpgsql_resolve_polymorphic_argtypes(procStruct->pronargs,
|
||||||
* parameter type from the caller's FuncExpr node, if we have one.
|
hashkey->argtypes,
|
||||||
* (In validation mode we arbitrarily assume we are dealing with
|
NULL,
|
||||||
* integers. This lets us build a valid, if possibly useless,
|
fcinfo->flinfo->fn_expr,
|
||||||
* function hashtable entry.)
|
forValidator,
|
||||||
*
|
NameStr(procStruct->proname));
|
||||||
* We can support arguments of type ANY the same way as normal
|
}
|
||||||
* polymorphic arguments.
|
}
|
||||||
*/
|
|
||||||
if (argtypeid == ANYARRAYOID || argtypeid == ANYELEMENTOID ||
|
/*
|
||||||
argtypeid == ANYOID)
|
* 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)
|
case ANYELEMENTOID:
|
||||||
argtypeid = INT4ARRAYOID;
|
argtypes[i] = INT4OID;
|
||||||
else
|
break;
|
||||||
argtypeid = INT4OID;
|
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
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.
|
* 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 void plpgsql_exec_error_callback(void *arg);
|
||||||
static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
|
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,
|
static int exec_stmt_block(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_stmt_block *block);
|
PLpgSQL_stmt_block *block);
|
||||||
@ -212,11 +210,11 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
|
|||||||
* Make local execution copies of all the datums
|
* Make local execution copies of all the datums
|
||||||
*/
|
*/
|
||||||
estate.err_text = gettext_noop("during initialization of execution state");
|
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]);
|
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");
|
estate.err_text = gettext_noop("while storing call arguments into local variables");
|
||||||
for (i = 0; i < func->fn_nargs; i++)
|
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
|
* 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
|
* Make local execution copies of all the datums
|
||||||
*/
|
*/
|
||||||
estate.err_text = gettext_noop("during initialization of execution state");
|
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]);
|
estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -551,7 +519,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
|
|||||||
var->freeval = false;
|
var->freeval = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Store the actual call argument values into the special execution
|
* Store the trigger argument values into the special execution
|
||||||
* state variables
|
* state variables
|
||||||
*/
|
*/
|
||||||
estate.err_text = gettext_noop("while storing call arguments into local 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]));
|
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
|
* Set the magic variable FOUND to false
|
||||||
*/
|
*/
|
||||||
@ -710,67 +647,65 @@ plpgsql_exec_error_callback(void *arg)
|
|||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* Support functions for copying local execution variables
|
* Support function for initializing 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.
|
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static PLpgSQL_datum *
|
static PLpgSQL_datum *
|
||||||
copy_plpgsql_datum(PLpgSQL_datum *datum)
|
copy_plpgsql_datum(PLpgSQL_datum *datum)
|
||||||
{
|
{
|
||||||
PLpgSQL_datum *result = NULL;
|
PLpgSQL_datum *result;
|
||||||
|
|
||||||
switch (datum->dtype)
|
switch (datum->dtype)
|
||||||
{
|
{
|
||||||
case PLPGSQL_DTYPE_VAR:
|
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:
|
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_ROW:
|
||||||
case PLPGSQL_DTYPE_RECFIELD:
|
case PLPGSQL_DTYPE_RECFIELD:
|
||||||
case PLPGSQL_DTYPE_ARRAYELEM:
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
||||||
case PLPGSQL_DTYPE_TRIGARG:
|
case PLPGSQL_DTYPE_TRIGARG:
|
||||||
|
/*
|
||||||
|
* These datum records are read-only at runtime, so no need
|
||||||
|
* to copy them
|
||||||
|
*/
|
||||||
result = datum;
|
result = datum;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
|
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
|
||||||
|
result = NULL; /* keep compiler quiet */
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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
|
static bool
|
||||||
exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond)
|
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)
|
if (estate->retisset)
|
||||||
return PLPGSQL_RC_RETURN;
|
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 */
|
PLpgSQL_datum *retvar = estate->datums[stmt->retvarno];
|
||||||
estate->retval = (Datum) 0;
|
|
||||||
estate->rettupdesc = NULL;
|
|
||||||
estate->retisnull = true;
|
|
||||||
|
|
||||||
if (stmt->retrecno >= 0)
|
switch (retvar->dtype)
|
||||||
{
|
{
|
||||||
PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->retrecno]);
|
case PLPGSQL_DTYPE_VAR:
|
||||||
|
|
||||||
if (HeapTupleIsValid(rec->tup))
|
|
||||||
{
|
{
|
||||||
estate->retval = (Datum) rec->tup;
|
PLpgSQL_var *var = (PLpgSQL_var *) retvar;
|
||||||
estate->rettupdesc = rec->tupdesc;
|
|
||||||
estate->retisnull = false;
|
estate->retval = var->value;
|
||||||
|
estate->retisnull = var->isnull;
|
||||||
|
estate->rettype = var->datatype->typoid;
|
||||||
}
|
}
|
||||||
return PLPGSQL_RC_RETURN;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
if (stmt->retrowno >= 0)
|
case PLPGSQL_DTYPE_REC:
|
||||||
{
|
|
||||||
PLpgSQL_row *row = (PLpgSQL_row *) (estate->datums[stmt->retrowno]);
|
|
||||||
|
|
||||||
if (row->rowtupdesc) /* should always be true here */
|
|
||||||
{
|
{
|
||||||
|
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,
|
estate->retval = (Datum) make_tuple_from_row(estate, row,
|
||||||
row->rowtupdesc);
|
row->rowtupdesc);
|
||||||
if (estate->retval == (Datum) NULL) /* should not happen */
|
if (estate->retval == (Datum) NULL) /* should not happen */
|
||||||
elog(ERROR, "row not compatible with its own tupdesc");
|
elog(ERROR, "row not compatible with its own tupdesc");
|
||||||
estate->rettupdesc = row->rowtupdesc;
|
estate->rettupdesc = row->rowtupdesc;
|
||||||
estate->retisnull = false;
|
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);
|
exec_run_select(estate, stmt->expr, 1, NULL);
|
||||||
if (estate->eval_processed > 0)
|
if (estate->eval_processed > 0)
|
||||||
@ -1728,24 +1684,23 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
|
|||||||
estate->retisnull = false;
|
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)
|
if (estate->fn_rettype == VOIDOID)
|
||||||
{
|
{
|
||||||
/* Special hack for function returning VOID */
|
/* Special hack for function returning VOID */
|
||||||
Assert(stmt->expr == NULL);
|
Assert(stmt->retvarno < 0 && stmt->expr == NULL);
|
||||||
estate->retval = (Datum) 0;
|
estate->retval = (Datum) 0;
|
||||||
estate->retisnull = false;
|
estate->retisnull = false;
|
||||||
estate->rettype = VOIDOID;
|
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;
|
return PLPGSQL_RC_RETURN;
|
||||||
}
|
}
|
||||||
@ -1777,37 +1732,78 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
|
|||||||
tupdesc = estate->rettupdesc;
|
tupdesc = estate->rettupdesc;
|
||||||
natts = tupdesc->natts;
|
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))
|
switch (retvar->dtype)
|
||||||
ereport(ERROR,
|
{
|
||||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
case PLPGSQL_DTYPE_VAR:
|
||||||
errmsg("record \"%s\" is not assigned yet",
|
{
|
||||||
rec->refname),
|
PLpgSQL_var *var = (PLpgSQL_var *) retvar;
|
||||||
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
Datum retval = var->value;
|
||||||
if (!compatible_tupdesc(tupdesc, rec->tupdesc))
|
bool isNull = var->isnull;
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
if (natts != 1)
|
||||||
errmsg("wrong record type supplied in RETURN NEXT")));
|
ereport(ERROR,
|
||||||
tuple = rec->tup;
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||||
}
|
errmsg("wrong result type supplied in RETURN NEXT")));
|
||||||
else if (stmt->row)
|
|
||||||
{
|
/* coerce type if needed */
|
||||||
tuple = make_tuple_from_row(estate, stmt->row, tupdesc);
|
retval = exec_simple_cast_value(retval,
|
||||||
if (tuple == NULL)
|
var->datatype->typoid,
|
||||||
ereport(ERROR,
|
tupdesc->attrs[0]->atttypid,
|
||||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
tupdesc->attrs[0]->atttypmod,
|
||||||
errmsg("wrong record type supplied in RETURN NEXT")));
|
&isNull);
|
||||||
free_tuple = true;
|
|
||||||
|
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)
|
else if (stmt->expr)
|
||||||
{
|
{
|
||||||
Datum retval;
|
Datum retval;
|
||||||
bool isNull;
|
bool isNull;
|
||||||
Oid rettype;
|
Oid rettype;
|
||||||
char nullflag;
|
|
||||||
|
|
||||||
if (natts != 1)
|
if (natts != 1)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -1826,9 +1822,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
|
|||||||
tupdesc->attrs[0]->atttypmod,
|
tupdesc->attrs[0]->atttypmod,
|
||||||
&isNull);
|
&isNull);
|
||||||
|
|
||||||
nullflag = isNull ? 'n' : ' ';
|
tuple = heap_form_tuple(tupdesc, &retval, &isNull);
|
||||||
|
|
||||||
tuple = heap_formtuple(tupdesc, &retval, &nullflag);
|
|
||||||
|
|
||||||
free_tuple = true;
|
free_tuple = true;
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -857,14 +857,12 @@ dump_return(PLpgSQL_stmt_return *stmt)
|
|||||||
{
|
{
|
||||||
dump_ind();
|
dump_ind();
|
||||||
printf("RETURN ");
|
printf("RETURN ");
|
||||||
if (stmt->retrecno >= 0)
|
if (stmt->retvarno >= 0)
|
||||||
printf("record %d", stmt->retrecno);
|
printf("variable %d", stmt->retvarno);
|
||||||
else if (stmt->retrowno >= 0)
|
else if (stmt->expr != NULL)
|
||||||
printf("row %d", stmt->retrowno);
|
|
||||||
else if (stmt->expr == NULL)
|
|
||||||
printf("NULL");
|
|
||||||
else
|
|
||||||
dump_expr(stmt->expr);
|
dump_expr(stmt->expr);
|
||||||
|
else
|
||||||
|
printf("NULL");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -873,12 +871,12 @@ dump_return_next(PLpgSQL_stmt_return_next *stmt)
|
|||||||
{
|
{
|
||||||
dump_ind();
|
dump_ind();
|
||||||
printf("RETURN NEXT ");
|
printf("RETURN NEXT ");
|
||||||
if (stmt->rec != NULL)
|
if (stmt->retvarno >= 0)
|
||||||
printf("target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
|
printf("variable %d", stmt->retvarno);
|
||||||
else if (stmt->row != NULL)
|
|
||||||
printf("target = %d %s\n", stmt->row->rowno, stmt->row->refname);
|
|
||||||
else if (stmt->expr != NULL)
|
else if (stmt->expr != NULL)
|
||||||
dump_expr(stmt->expr);
|
dump_expr(stmt->expr);
|
||||||
|
else
|
||||||
|
printf("NULL");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -491,17 +491,15 @@ typedef struct
|
|||||||
int cmd_type;
|
int cmd_type;
|
||||||
int lineno;
|
int lineno;
|
||||||
PLpgSQL_expr *expr;
|
PLpgSQL_expr *expr;
|
||||||
int retrecno;
|
int retvarno;
|
||||||
int retrowno;
|
|
||||||
} PLpgSQL_stmt_return;
|
} PLpgSQL_stmt_return;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{ /* RETURN NEXT statement */
|
{ /* RETURN NEXT statement */
|
||||||
int cmd_type;
|
int cmd_type;
|
||||||
int lineno;
|
int lineno;
|
||||||
PLpgSQL_rec *rec;
|
|
||||||
PLpgSQL_row *row;
|
|
||||||
PLpgSQL_expr *expr;
|
PLpgSQL_expr *expr;
|
||||||
|
int retvarno;
|
||||||
} PLpgSQL_stmt_return_next;
|
} PLpgSQL_stmt_return_next;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@ -572,6 +570,7 @@ typedef struct PLpgSQL_function
|
|||||||
|
|
||||||
int fn_nargs;
|
int fn_nargs;
|
||||||
int fn_argvarnos[FUNC_MAX_ARGS];
|
int fn_argvarnos[FUNC_MAX_ARGS];
|
||||||
|
int out_param_varno;
|
||||||
int found_varno;
|
int found_varno;
|
||||||
int new_varno;
|
int new_varno;
|
||||||
int old_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
|
50 | 5 | xxx
|
||||||
(1 row)
|
(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
|
-- 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(1500) AS (a int, b int, c int);
|
||||||
SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text);
|
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
|
-- test PERFORM
|
||||||
--
|
--
|
||||||
@ -1917,4 +2000,4 @@ end;$$ language plpgsql;
|
|||||||
create function void_return_expr() returns void as $$
|
create function void_return_expr() returns void as $$
|
||||||
begin
|
begin
|
||||||
return 5;
|
return 5;
|
||||||
end;$$ language plpgsql;
|
end;$$ language plpgsql;
|
||||||
|
Reference in New Issue
Block a user