mirror of
https://github.com/postgres/postgres.git
synced 2025-04-21 12:05:57 +03:00
Make plpgsql support FOR over a query specified by a cursor declaration,
for improved compatibility with Oracle. Pavel Stehule, with some fixes by me.
This commit is contained in:
parent
2604359251
commit
347dd6a1cf
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.126 2008/04/01 03:51:09 tgl Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.127 2008/04/06 23:43:29 tgl Exp $ -->
|
||||
|
||||
<chapter id="plpgsql">
|
||||
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
|
||||
@ -286,9 +286,11 @@ $$ LANGUAGE plpgsql;
|
||||
<para>
|
||||
All variables used in a block must be declared in the
|
||||
declarations section of the block.
|
||||
(The only exception is that the loop variable of a <literal>FOR</> loop
|
||||
(The only exceptions are that the loop variable of a <literal>FOR</> loop
|
||||
iterating over a range of integer values is automatically declared as an
|
||||
integer variable.)
|
||||
integer variable, and likewise the loop variable of a <literal>FOR</> loop
|
||||
iterating over a cursor's result is automatically declared as a
|
||||
record variable.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -1281,50 +1283,51 @@ GET DIAGNOSTICS integer_var = ROW_COUNT;
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
A <command>SELECT INTO</command> statement sets
|
||||
<literal>FOUND</literal> true if a row is assigned, false if no
|
||||
row is returned.
|
||||
A <command>SELECT INTO</command> statement sets
|
||||
<literal>FOUND</literal> true if a row is assigned, false if no
|
||||
row is returned.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
A <command>PERFORM</> statement sets <literal>FOUND</literal>
|
||||
true if it produces (and discards) one or more rows, false if
|
||||
no row is produced.
|
||||
A <command>PERFORM</> statement sets <literal>FOUND</literal>
|
||||
true if it produces (and discards) one or more rows, false if
|
||||
no row is produced.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<command>UPDATE</>, <command>INSERT</>, and <command>DELETE</>
|
||||
statements set <literal>FOUND</literal> true if at least one
|
||||
row is affected, false if no row is affected.
|
||||
<command>UPDATE</>, <command>INSERT</>, and <command>DELETE</>
|
||||
statements set <literal>FOUND</literal> true if at least one
|
||||
row is affected, false if no row is affected.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
A <command>FETCH</> statement sets <literal>FOUND</literal>
|
||||
true if it returns a row, false if no row is returned.
|
||||
A <command>FETCH</> statement sets <literal>FOUND</literal>
|
||||
true if it returns a row, false if no row is returned.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
A <command>MOVE</> statement sets <literal>FOUND</literal>
|
||||
true if it successfully repositions the cursor, false otherwise.
|
||||
A <command>MOVE</> statement sets <literal>FOUND</literal>
|
||||
true if it successfully repositions the cursor, false otherwise.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
A <command>FOR</> statement sets <literal>FOUND</literal> true
|
||||
if it iterates one or more times, else false. This applies to
|
||||
all three variants of the <command>FOR</> statement (integer
|
||||
<command>FOR</> loops, record-set <command>FOR</> loops, and
|
||||
dynamic record-set <command>FOR</>
|
||||
loops). <literal>FOUND</literal> is set this way when the
|
||||
<command>FOR</> loop exits; inside the execution of the loop,
|
||||
<literal>FOUND</literal> is not modified by the
|
||||
<command>FOR</> statement, although it might be changed by the
|
||||
execution of other statements within the loop body.
|
||||
A <command>FOR</> statement sets <literal>FOUND</literal> true
|
||||
if it iterates one or more times, else false. This applies to
|
||||
all four variants of the <command>FOR</> statement (integer
|
||||
<command>FOR</> loops, record-set <command>FOR</> loops,
|
||||
dynamic record-set <command>FOR</> loops, and cursor
|
||||
<command>FOR</> loops).
|
||||
<literal>FOUND</literal> is set this way when the
|
||||
<command>FOR</> loop exits; inside the execution of the loop,
|
||||
<literal>FOUND</literal> is not modified by the
|
||||
<command>FOR</> statement, although it might be changed by the
|
||||
execution of other statements within the loop body.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
@ -2057,6 +2060,12 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
|
||||
As with <command>EXECUTE</command>, parameter values can be inserted
|
||||
into the dynamic command via <literal>USING</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Another way to specify the query whose results should be iterated
|
||||
through is to declare it as a cursor. This is described in
|
||||
<xref linkend="plpgsql-cursor-for-loop">.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="plpgsql-error-trapping">
|
||||
@ -2293,6 +2302,14 @@ DECLARE
|
||||
cursor variables while the third uses a bound cursor variable.
|
||||
</para>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
Bound cursors can also be used without explicitly opening them,
|
||||
via the <command>FOR</> statement described in
|
||||
<xref linkend="plpgsql-cursor-for-loop">.
|
||||
</para>
|
||||
</note>
|
||||
|
||||
<sect3>
|
||||
<title><command>OPEN FOR</command> <replaceable>query</replaceable></title>
|
||||
|
||||
@ -2676,6 +2693,36 @@ COMMIT;
|
||||
</para>
|
||||
</sect3>
|
||||
</sect2>
|
||||
|
||||
<sect2 id="plpgsql-cursor-for-loop">
|
||||
<title>Looping Through a Cursor's Result</title>
|
||||
|
||||
<para>
|
||||
There is a variant of the <command>FOR</> statement that allows
|
||||
iterating through the rows returned by a cursor. The syntax is:
|
||||
|
||||
<synopsis>
|
||||
<optional> <<<replaceable>label</replaceable>>> </optional>
|
||||
FOR <replaceable>recordvar</replaceable> IN <replaceable>bound_cursor</replaceable> <optional> ( <replaceable>argument_values</replaceable> ) </optional> LOOP
|
||||
<replaceable>statements</replaceable>
|
||||
END LOOP <optional> <replaceable>label</replaceable> </optional>;
|
||||
</synopsis>
|
||||
|
||||
The cursor variable must have been bound to some query when it was
|
||||
declared, and it <emphasis>cannot</> be open already. The
|
||||
<command>FOR</> statement automatically opens the cursor, and it closes
|
||||
the cursor again when the loop exits. A list of actual argument value
|
||||
expressions must appear if and only if the cursor was declared to take
|
||||
arguments. These values will be substituted in the query, in just
|
||||
the same way as during an <command>OPEN</>.
|
||||
The variable <replaceable>recordvar</replaceable> is automatically
|
||||
defined as type <type>record</> and exists only inside the loop (any
|
||||
existing definition of the variable name is ignored within the loop).
|
||||
Each row returned by the cursor is successively assigned to this
|
||||
record variable and the loop body is executed.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="plpgsql-errors-and-messages">
|
||||
@ -3796,14 +3843,6 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
No need for cursors in <application>PL/pgSQL</>, just put the
|
||||
query in the <literal>FOR</literal> statement. (See <xref
|
||||
linkend="plpgsql-porting-ex2">.)
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
In <productname>PostgreSQL</> the function body must be written as
|
||||
@ -3840,6 +3879,23 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<command>FOR</> loops over queries (other than cursors) also work
|
||||
differently: the target variable(s) must have been declared,
|
||||
whereas <application>PL/SQL</> always declares them implicitly.
|
||||
An advantage of this is that the variable values are still accessible
|
||||
after the loop exits.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
There are various notational differences for the use of cursor
|
||||
variables.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
@ -3939,8 +3995,7 @@ $$ LANGUAGE plpgsql;
|
||||
The following procedure grabs rows from a
|
||||
<command>SELECT</command> statement and builds a large function
|
||||
with the results in <literal>IF</literal> statements, for the
|
||||
sake of efficiency. Notice particularly the differences in the
|
||||
cursor and the <literal>FOR</literal> loop.
|
||||
sake of efficiency.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -3950,7 +4005,6 @@ CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS
|
||||
CURSOR referrer_keys IS
|
||||
SELECT * FROM cs_referrer_keys
|
||||
ORDER BY try_order;
|
||||
|
||||
func_cmd VARCHAR(4000);
|
||||
BEGIN
|
||||
func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR,
|
||||
@ -3978,16 +4032,15 @@ show errors;
|
||||
<programlisting>
|
||||
CREATE OR REPLACE FUNCTION cs_update_referrer_type_proc() RETURNS void AS $func$
|
||||
DECLARE
|
||||
referrer_key RECORD; -- declare a generic record to be used in a FOR
|
||||
CURSOR referrer_keys IS
|
||||
SELECT * FROM cs_referrer_keys
|
||||
ORDER BY try_order;
|
||||
func_body text;
|
||||
func_cmd text;
|
||||
BEGIN
|
||||
func_body := 'BEGIN';
|
||||
|
||||
-- Notice how we scan through the results of a query in a FOR loop
|
||||
-- using the FOR <record> construct.
|
||||
|
||||
FOR referrer_key IN SELECT * FROM cs_referrer_keys ORDER BY try_order LOOP
|
||||
FOR referrer_key IN referrer_keys LOOP
|
||||
func_body := func_body ||
|
||||
' IF v_' || referrer_key.kind
|
||||
|| ' LIKE ' || quote_literal(referrer_key.key_string)
|
||||
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.109 2008/04/01 03:51:09 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.110 2008/04/06 23:43:29 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -50,6 +50,8 @@ static void plpgsql_sql_error_callback(void *arg);
|
||||
static char *check_label(const char *yytxt);
|
||||
static void check_labels(const char *start_label,
|
||||
const char *end_label);
|
||||
static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
|
||||
int until, const char *expected);
|
||||
|
||||
%}
|
||||
|
||||
@ -861,21 +863,15 @@ stmt_for : opt_block_label K_FOR for_control loop_body
|
||||
new->body = $4.stmts;
|
||||
$$ = (PLpgSQL_stmt *) new;
|
||||
}
|
||||
else if ($3->cmd_type == PLPGSQL_STMT_FORS)
|
||||
{
|
||||
PLpgSQL_stmt_fors *new;
|
||||
|
||||
new = (PLpgSQL_stmt_fors *) $3;
|
||||
new->label = $1;
|
||||
new->body = $4.stmts;
|
||||
$$ = (PLpgSQL_stmt *) new;
|
||||
}
|
||||
else
|
||||
{
|
||||
PLpgSQL_stmt_dynfors *new;
|
||||
PLpgSQL_stmt_forq *new;
|
||||
|
||||
Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
|
||||
new = (PLpgSQL_stmt_dynfors *) $3;
|
||||
Assert($3->cmd_type == PLPGSQL_STMT_FORS ||
|
||||
$3->cmd_type == PLPGSQL_STMT_FORC ||
|
||||
$3->cmd_type == PLPGSQL_STMT_DYNFORS);
|
||||
/* forq is the common supertype of all three */
|
||||
new = (PLpgSQL_stmt_forq *) $3;
|
||||
new->label = $1;
|
||||
new->body = $4.stmts;
|
||||
$$ = (PLpgSQL_stmt *) new;
|
||||
@ -892,9 +888,9 @@ for_control :
|
||||
{
|
||||
int tok = yylex();
|
||||
|
||||
/* Simple case: EXECUTE is a dynamic FOR loop */
|
||||
if (tok == K_EXECUTE)
|
||||
{
|
||||
/* EXECUTE means it's a dynamic FOR loop */
|
||||
PLpgSQL_stmt_dynfors *new;
|
||||
PLpgSQL_expr *expr;
|
||||
int term;
|
||||
@ -942,6 +938,47 @@ for_control :
|
||||
|
||||
$$ = (PLpgSQL_stmt *) new;
|
||||
}
|
||||
else if (tok == T_SCALAR &&
|
||||
yylval.scalar->dtype == PLPGSQL_DTYPE_VAR &&
|
||||
((PLpgSQL_var *) yylval.scalar)->datatype->typoid == REFCURSOROID)
|
||||
{
|
||||
/* It's FOR var IN cursor */
|
||||
PLpgSQL_stmt_forc *new;
|
||||
PLpgSQL_var *cursor = (PLpgSQL_var *) yylval.scalar;
|
||||
char *varname;
|
||||
|
||||
new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc));
|
||||
new->cmd_type = PLPGSQL_STMT_FORC;
|
||||
new->lineno = $1;
|
||||
|
||||
new->curvar = cursor->varno;
|
||||
|
||||
/* Should have had a single variable name */
|
||||
plpgsql_error_lineno = $2.lineno;
|
||||
if ($2.scalar && $2.row)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cursor FOR loop must have just one target variable")));
|
||||
|
||||
/* create loop's private RECORD variable */
|
||||
plpgsql_convert_ident($2.name, &varname, 1);
|
||||
new->rec = plpgsql_build_record(varname,
|
||||
$2.lineno,
|
||||
true);
|
||||
|
||||
/* can't use an unbound cursor this way */
|
||||
if (cursor->cursor_explicit_expr == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cursor FOR loop must use a bound cursor variable")));
|
||||
|
||||
/* collect cursor's parameters if any */
|
||||
new->argquery = read_cursor_args(cursor,
|
||||
K_LOOP,
|
||||
"LOOP");
|
||||
|
||||
$$ = (PLpgSQL_stmt *) new;
|
||||
}
|
||||
else
|
||||
{
|
||||
PLpgSQL_expr *expr1;
|
||||
@ -1412,81 +1449,8 @@ stmt_open : K_OPEN lno cursor_variable
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($3->cursor_explicit_argrow >= 0)
|
||||
{
|
||||
char *cp;
|
||||
|
||||
tok = yylex();
|
||||
if (tok != '(')
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cursor \"%s\" has arguments",
|
||||
$3->refname)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Push back the '(', else read_sql_stmt
|
||||
* will complain about unbalanced parens.
|
||||
*/
|
||||
plpgsql_push_back_token(tok);
|
||||
|
||||
new->argquery = read_sql_stmt("SELECT ");
|
||||
|
||||
/*
|
||||
* Now remove the leading and trailing parens,
|
||||
* because we want "select 1, 2", not
|
||||
* "select (1, 2)".
|
||||
*/
|
||||
cp = new->argquery->query;
|
||||
|
||||
if (strncmp(cp, "SELECT", 6) != 0)
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
/* internal error */
|
||||
elog(ERROR, "expected \"SELECT (\", got \"%s\"",
|
||||
new->argquery->query);
|
||||
}
|
||||
cp += 6;
|
||||
while (*cp == ' ') /* could be more than 1 space here */
|
||||
cp++;
|
||||
if (*cp != '(')
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
/* internal error */
|
||||
elog(ERROR, "expected \"SELECT (\", got \"%s\"",
|
||||
new->argquery->query);
|
||||
}
|
||||
*cp = ' ';
|
||||
|
||||
cp += strlen(cp) - 1;
|
||||
|
||||
if (*cp != ')')
|
||||
yyerror("expected \")\"");
|
||||
*cp = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
tok = yylex();
|
||||
if (tok == '(')
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cursor \"%s\" has no arguments",
|
||||
$3->refname)));
|
||||
}
|
||||
|
||||
if (tok != ';')
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("syntax error at \"%s\"",
|
||||
yytext)));
|
||||
}
|
||||
}
|
||||
/* predefined cursor query, so read args */
|
||||
new->argquery = read_cursor_args($3, ';', ";");
|
||||
}
|
||||
|
||||
$$ = (PLpgSQL_stmt *)new;
|
||||
@ -2578,6 +2542,97 @@ check_labels(const char *start_label, const char *end_label)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the arguments (if any) for a cursor, followed by the until token
|
||||
*
|
||||
* If cursor has no args, just swallow the until token and return NULL.
|
||||
* If it does have args, we expect to see "( expr [, expr ...] )" followed
|
||||
* by the until token. Consume all that and return a SELECT query that
|
||||
* evaluates the expression(s) (without the outer parens).
|
||||
*/
|
||||
static PLpgSQL_expr *
|
||||
read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
|
||||
{
|
||||
PLpgSQL_expr *expr;
|
||||
int tok;
|
||||
char *cp;
|
||||
|
||||
tok = yylex();
|
||||
if (cursor->cursor_explicit_argrow < 0)
|
||||
{
|
||||
/* No arguments expected */
|
||||
if (tok == '(')
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cursor \"%s\" has no arguments",
|
||||
cursor->refname)));
|
||||
}
|
||||
|
||||
if (tok != until)
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("syntax error at \"%s\"",
|
||||
yytext)));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Else better provide arguments */
|
||||
if (tok != '(')
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("cursor \"%s\" has arguments",
|
||||
cursor->refname)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Push back the '(', else plpgsql_read_expression
|
||||
* will complain about unbalanced parens.
|
||||
*/
|
||||
plpgsql_push_back_token(tok);
|
||||
|
||||
expr = plpgsql_read_expression(until, expected);
|
||||
|
||||
/*
|
||||
* Now remove the leading and trailing parens,
|
||||
* because we want "SELECT 1, 2", not "SELECT (1, 2)".
|
||||
*/
|
||||
cp = expr->query;
|
||||
|
||||
if (strncmp(cp, "SELECT", 6) != 0)
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
/* internal error */
|
||||
elog(ERROR, "expected \"SELECT (\", got \"%s\"", expr->query);
|
||||
}
|
||||
cp += 6;
|
||||
while (*cp == ' ') /* could be more than 1 space here */
|
||||
cp++;
|
||||
if (*cp != '(')
|
||||
{
|
||||
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
||||
/* internal error */
|
||||
elog(ERROR, "expected \"SELECT (\", got \"%s\"", expr->query);
|
||||
}
|
||||
*cp = ' ';
|
||||
|
||||
cp += strlen(cp) - 1;
|
||||
|
||||
if (*cp != ')')
|
||||
yyerror("expected \")\"");
|
||||
*cp = '\0';
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
|
||||
/* Needed to avoid conflict between different prefix settings: */
|
||||
#undef yylex
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.123 2008/03/27 03:57:34 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.124 2008/04/06 23:43:29 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -575,25 +575,11 @@ do_compile(FunctionCallInfo fcinfo,
|
||||
errhint("You probably want to use TG_NARGS and TG_ARGV instead.")));
|
||||
|
||||
/* Add the record for referencing NEW */
|
||||
rec = palloc0(sizeof(PLpgSQL_rec));
|
||||
rec->dtype = PLPGSQL_DTYPE_REC;
|
||||
rec->refname = pstrdup("new");
|
||||
rec->tup = NULL;
|
||||
rec->tupdesc = NULL;
|
||||
rec->freetup = false;
|
||||
plpgsql_adddatum((PLpgSQL_datum *) rec);
|
||||
plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
|
||||
rec = plpgsql_build_record("new", 0, true);
|
||||
function->new_varno = rec->recno;
|
||||
|
||||
/* Add the record for referencing OLD */
|
||||
rec = palloc0(sizeof(PLpgSQL_rec));
|
||||
rec->dtype = PLPGSQL_DTYPE_REC;
|
||||
rec->refname = pstrdup("old");
|
||||
rec->tup = NULL;
|
||||
rec->tupdesc = NULL;
|
||||
rec->freetup = false;
|
||||
plpgsql_adddatum((PLpgSQL_datum *) rec);
|
||||
plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
|
||||
rec = plpgsql_build_record("old", 0, true);
|
||||
function->old_varno = rec->recno;
|
||||
|
||||
/* Add the variable tg_name */
|
||||
@ -1481,21 +1467,10 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
|
||||
}
|
||||
case PLPGSQL_TTYPE_REC:
|
||||
{
|
||||
/*
|
||||
* "record" type -- build a variable-contents record variable
|
||||
*/
|
||||
/* "record" type -- build a record variable */
|
||||
PLpgSQL_rec *rec;
|
||||
|
||||
rec = palloc0(sizeof(PLpgSQL_rec));
|
||||
rec->dtype = PLPGSQL_DTYPE_REC;
|
||||
rec->refname = pstrdup(refname);
|
||||
rec->lineno = lineno;
|
||||
|
||||
plpgsql_adddatum((PLpgSQL_datum *) rec);
|
||||
if (add2namespace)
|
||||
plpgsql_ns_additem(PLPGSQL_NSTYPE_REC,
|
||||
rec->recno,
|
||||
refname);
|
||||
rec = plpgsql_build_record(refname, lineno, add2namespace);
|
||||
result = (PLpgSQL_variable *) rec;
|
||||
break;
|
||||
}
|
||||
@ -1515,6 +1490,28 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build empty named record variable, and optionally add it to namespace
|
||||
*/
|
||||
PLpgSQL_rec *
|
||||
plpgsql_build_record(const char *refname, int lineno, bool add2namespace)
|
||||
{
|
||||
PLpgSQL_rec *rec;
|
||||
|
||||
rec = palloc0(sizeof(PLpgSQL_rec));
|
||||
rec->dtype = PLPGSQL_DTYPE_REC;
|
||||
rec->refname = pstrdup(refname);
|
||||
rec->lineno = lineno;
|
||||
rec->tup = NULL;
|
||||
rec->tupdesc = NULL;
|
||||
rec->freetup = false;
|
||||
plpgsql_adddatum((PLpgSQL_datum *) rec);
|
||||
if (add2namespace)
|
||||
plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
|
||||
|
||||
return rec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build a row-variable data structure given the pg_class OID.
|
||||
*/
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.208 2008/04/01 03:51:09 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.209 2008/04/06 23:43:29 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -103,6 +103,8 @@ static int exec_stmt_fori(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_stmt_fori *stmt);
|
||||
static int exec_stmt_fors(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_stmt_fors *stmt);
|
||||
static int exec_stmt_forc(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_stmt_forc *stmt);
|
||||
static int exec_stmt_open(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_stmt_open *stmt);
|
||||
static int exec_stmt_fetch(PLpgSQL_execstate *estate,
|
||||
@ -165,6 +167,10 @@ static Datum exec_eval_expr(PLpgSQL_execstate *estate,
|
||||
Oid *rettype);
|
||||
static int exec_run_select(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_expr *expr, long maxtuples, Portal *portalP);
|
||||
static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
|
||||
Portal portal, bool prefetch_ok);
|
||||
static void eval_expr_params(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_expr *expr, Datum **p_values, char **p_nulls);
|
||||
static void exec_move_row(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_rec *rec,
|
||||
PLpgSQL_row *row,
|
||||
@ -187,6 +193,7 @@ static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
|
||||
static void exec_set_found(PLpgSQL_execstate *estate, bool state);
|
||||
static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
|
||||
static void free_var(PLpgSQL_var *var);
|
||||
static void assign_text_var(PLpgSQL_var *var, const char *str);
|
||||
static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
|
||||
List *params);
|
||||
static void free_params_data(PreparedParamsData *ppd);
|
||||
@ -1084,15 +1091,12 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
|
||||
|
||||
state_var = (PLpgSQL_var *)
|
||||
estate->datums[block->exceptions->sqlstate_varno];
|
||||
state_var->value = CStringGetTextDatum(unpack_sql_state(edata->sqlerrcode));
|
||||
state_var->freeval = true;
|
||||
state_var->isnull = false;
|
||||
|
||||
errm_var = (PLpgSQL_var *)
|
||||
estate->datums[block->exceptions->sqlerrm_varno];
|
||||
errm_var->value = CStringGetTextDatum(edata->message);
|
||||
errm_var->freeval = true;
|
||||
errm_var->isnull = false;
|
||||
|
||||
assign_text_var(state_var,
|
||||
unpack_sql_state(edata->sqlerrcode));
|
||||
assign_text_var(errm_var, edata->message);
|
||||
|
||||
estate->err_text = NULL;
|
||||
|
||||
@ -1100,8 +1104,10 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
|
||||
|
||||
free_var(state_var);
|
||||
state_var->value = (Datum) 0;
|
||||
state_var->isnull = true;
|
||||
free_var(errm_var);
|
||||
errm_var->value = (Datum) 0;
|
||||
errm_var->isnull = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1246,6 +1252,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
|
||||
rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt);
|
||||
break;
|
||||
|
||||
case PLPGSQL_STMT_FORC:
|
||||
rc = exec_stmt_forc(estate, (PLpgSQL_stmt_forc *) stmt);
|
||||
break;
|
||||
|
||||
case PLPGSQL_STMT_EXIT:
|
||||
rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt);
|
||||
break;
|
||||
@ -1724,145 +1734,149 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
|
||||
static int
|
||||
exec_stmt_fors(PLpgSQL_execstate *estate, PLpgSQL_stmt_fors *stmt)
|
||||
{
|
||||
PLpgSQL_rec *rec = NULL;
|
||||
PLpgSQL_row *row = NULL;
|
||||
SPITupleTable *tuptab;
|
||||
Portal portal;
|
||||
bool found = false;
|
||||
int rc = PLPGSQL_RC_OK;
|
||||
int i;
|
||||
int n;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Determine if we assign to a record or a row
|
||||
*/
|
||||
if (stmt->rec != NULL)
|
||||
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||
else if (stmt->row != NULL)
|
||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||
else
|
||||
elog(ERROR, "unsupported target");
|
||||
|
||||
/*
|
||||
* Open the implicit cursor for the statement and fetch the initial 10
|
||||
* rows.
|
||||
* Open the implicit cursor for the statement using exec_run_select
|
||||
*/
|
||||
exec_run_select(estate, stmt->query, 0, &portal);
|
||||
|
||||
SPI_cursor_fetch(portal, true, 10);
|
||||
tuptab = SPI_tuptable;
|
||||
n = SPI_processed;
|
||||
|
||||
/*
|
||||
* If the query didn't return any rows, set the target to NULL and return
|
||||
* with FOUND = false.
|
||||
* Execute the loop
|
||||
*/
|
||||
if (n == 0)
|
||||
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
|
||||
else
|
||||
found = true; /* processed at least one tuple */
|
||||
|
||||
/*
|
||||
* Now do the loop
|
||||
*/
|
||||
while (n > 0)
|
||||
{
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
/*
|
||||
* Assign the tuple to the target
|
||||
*/
|
||||
exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
|
||||
|
||||
/*
|
||||
* Execute the statements
|
||||
*/
|
||||
rc = exec_stmts(estate, stmt->body);
|
||||
if (rc != PLPGSQL_RC_OK)
|
||||
{
|
||||
if (rc == PLPGSQL_RC_EXIT)
|
||||
{
|
||||
if (estate->exitlabel == NULL)
|
||||
/* unlabelled exit, finish the current loop */
|
||||
rc = PLPGSQL_RC_OK;
|
||||
else if (stmt->label != NULL &&
|
||||
strcmp(stmt->label, estate->exitlabel) == 0)
|
||||
{
|
||||
/* labelled exit, matches the current stmt's label */
|
||||
estate->exitlabel = NULL;
|
||||
rc = PLPGSQL_RC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* otherwise, we processed a labelled exit that does not
|
||||
* match the current statement's label, if any: return
|
||||
* RC_EXIT so that the EXIT continues to recurse upward.
|
||||
*/
|
||||
}
|
||||
else if (rc == PLPGSQL_RC_CONTINUE)
|
||||
{
|
||||
if (estate->exitlabel == NULL)
|
||||
{
|
||||
/* anonymous continue, so re-run the current loop */
|
||||
rc = PLPGSQL_RC_OK;
|
||||
continue;
|
||||
}
|
||||
else if (stmt->label != NULL &&
|
||||
strcmp(stmt->label, estate->exitlabel) == 0)
|
||||
{
|
||||
/* label matches named continue, so re-run loop */
|
||||
rc = PLPGSQL_RC_OK;
|
||||
estate->exitlabel = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* otherwise, we processed a named continue that does not
|
||||
* match the current statement's label, if any: return
|
||||
* RC_CONTINUE so that the CONTINUE will propagate up the
|
||||
* stack.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* We're aborting the loop, so cleanup and set FOUND. (This
|
||||
* code should match the code after the loop.)
|
||||
*/
|
||||
SPI_freetuptable(tuptab);
|
||||
SPI_cursor_close(portal);
|
||||
exec_set_found(estate, found);
|
||||
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
SPI_freetuptable(tuptab);
|
||||
|
||||
/*
|
||||
* Fetch the next 50 tuples
|
||||
*/
|
||||
SPI_cursor_fetch(portal, true, 50);
|
||||
n = SPI_processed;
|
||||
tuptab = SPI_tuptable;
|
||||
}
|
||||
|
||||
/*
|
||||
* Release last group of tuples
|
||||
*/
|
||||
SPI_freetuptable(tuptab);
|
||||
rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true);
|
||||
|
||||
/*
|
||||
* Close the implicit cursor
|
||||
*/
|
||||
SPI_cursor_close(portal);
|
||||
|
||||
/*
|
||||
* Set the FOUND variable to indicate the result of executing the loop
|
||||
* (namely, whether we looped one or more times). This must be set here so
|
||||
* that it does not interfere with the value of the FOUND variable inside
|
||||
* the loop processing itself.
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* ----------
|
||||
* exec_stmt_forc Execute a loop for each row from a cursor.
|
||||
* ----------
|
||||
*/
|
||||
static int
|
||||
exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
|
||||
{
|
||||
PLpgSQL_var *curvar;
|
||||
char *curname = NULL;
|
||||
PLpgSQL_expr *query;
|
||||
Portal portal;
|
||||
int rc;
|
||||
Datum *values;
|
||||
char *nulls;
|
||||
|
||||
/* ----------
|
||||
* Get the cursor variable and if it has an assigned name, check
|
||||
* that it's not in use currently.
|
||||
* ----------
|
||||
*/
|
||||
exec_set_found(estate, found);
|
||||
curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
|
||||
if (!curvar->isnull)
|
||||
{
|
||||
curname = TextDatumGetCString(curvar->value);
|
||||
if (SPI_cursor_find(curname) != NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_CURSOR),
|
||||
errmsg("cursor \"%s\" already in use", curname)));
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* Open the cursor just like an OPEN command
|
||||
*
|
||||
* Note: parser should already have checked that statement supplies
|
||||
* args iff cursor needs them, but we check again to be safe.
|
||||
* ----------
|
||||
*/
|
||||
if (stmt->argquery != NULL)
|
||||
{
|
||||
/* ----------
|
||||
* OPEN CURSOR with args. We fake a SELECT ... INTO ...
|
||||
* statement to evaluate the args and put 'em into the
|
||||
* internal row.
|
||||
* ----------
|
||||
*/
|
||||
PLpgSQL_stmt_execsql set_args;
|
||||
|
||||
if (curvar->cursor_explicit_argrow < 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("arguments given for cursor without arguments")));
|
||||
|
||||
memset(&set_args, 0, sizeof(set_args));
|
||||
set_args.cmd_type = PLPGSQL_STMT_EXECSQL;
|
||||
set_args.lineno = stmt->lineno;
|
||||
set_args.sqlstmt = stmt->argquery;
|
||||
set_args.into = true;
|
||||
/* XXX historically this has not been STRICT */
|
||||
set_args.row = (PLpgSQL_row *)
|
||||
(estate->datums[curvar->cursor_explicit_argrow]);
|
||||
|
||||
if (exec_stmt_execsql(estate, &set_args) != PLPGSQL_RC_OK)
|
||||
elog(ERROR, "open cursor failed during argument processing");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (curvar->cursor_explicit_argrow >= 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("arguments required for cursor")));
|
||||
}
|
||||
|
||||
query = curvar->cursor_explicit_expr;
|
||||
Assert(query);
|
||||
|
||||
if (query->plan == NULL)
|
||||
exec_prepare_plan(estate, query, curvar->cursor_options);
|
||||
|
||||
/*
|
||||
* Now build up the values and nulls arguments for SPI_execute_plan()
|
||||
*/
|
||||
eval_expr_params(estate, query, &values, &nulls);
|
||||
|
||||
/*
|
||||
* Open the cursor
|
||||
*/
|
||||
portal = SPI_cursor_open(curname, query->plan, values, nulls,
|
||||
estate->readonly_func);
|
||||
if (portal == NULL)
|
||||
elog(ERROR, "could not open cursor: %s",
|
||||
SPI_result_code_string(SPI_result));
|
||||
|
||||
/*
|
||||
* If cursor variable was NULL, store the generated portal name in it
|
||||
*/
|
||||
if (curname == NULL)
|
||||
assign_text_var(curvar, portal->name);
|
||||
|
||||
/*
|
||||
* Execute the loop. We can't prefetch because the cursor is accessible
|
||||
* to the user, for instance via UPDATE WHERE CURRENT OF within the loop.
|
||||
*/
|
||||
rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, false);
|
||||
|
||||
/* ----------
|
||||
* Close portal, and restore cursor variable if it was initially NULL.
|
||||
* ----------
|
||||
*/
|
||||
SPI_cursor_close(portal);
|
||||
|
||||
if (curname == NULL)
|
||||
{
|
||||
free_var(curvar);
|
||||
curvar->value = (Datum) 0;
|
||||
curvar->isnull = true;
|
||||
}
|
||||
|
||||
pfree(values);
|
||||
pfree(nulls);
|
||||
if (curname)
|
||||
pfree(curname);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@ -2470,7 +2484,6 @@ static int
|
||||
exec_stmt_execsql(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_stmt_execsql *stmt)
|
||||
{
|
||||
int i;
|
||||
Datum *values;
|
||||
char *nulls;
|
||||
long tcount;
|
||||
@ -2511,22 +2524,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
|
||||
/*
|
||||
* Now build up the values and nulls arguments for SPI_execute_plan()
|
||||
*/
|
||||
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
|
||||
nulls = (char *) palloc(expr->nparams * sizeof(char));
|
||||
|
||||
for (i = 0; i < expr->nparams; i++)
|
||||
{
|
||||
PLpgSQL_datum *datum = estate->datums[expr->params[i]];
|
||||
Oid paramtypeid;
|
||||
bool paramisnull;
|
||||
|
||||
exec_eval_datum(estate, datum, expr->plan_argtypes[i],
|
||||
¶mtypeid, &values[i], ¶misnull);
|
||||
if (paramisnull)
|
||||
nulls[i] = 'n';
|
||||
else
|
||||
nulls[i] = ' ';
|
||||
}
|
||||
eval_expr_params(estate, expr, &values, &nulls);
|
||||
|
||||
/*
|
||||
* If we have INTO, then we only need one row back ... but if we have INTO
|
||||
@ -2846,22 +2844,8 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
|
||||
bool isnull;
|
||||
Oid restype;
|
||||
char *querystr;
|
||||
PLpgSQL_rec *rec = NULL;
|
||||
PLpgSQL_row *row = NULL;
|
||||
SPITupleTable *tuptab;
|
||||
int n;
|
||||
Portal portal;
|
||||
bool found = false;
|
||||
|
||||
/*
|
||||
* Determine if we assign to a record or a row
|
||||
*/
|
||||
if (stmt->rec != NULL)
|
||||
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||
else if (stmt->row != NULL)
|
||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||
else
|
||||
elog(ERROR, "unsupported target");
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Evaluate the string expression after the EXECUTE keyword. It's result
|
||||
@ -2910,124 +2894,16 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
|
||||
pfree(querystr);
|
||||
|
||||
/*
|
||||
* Fetch the initial 10 tuples
|
||||
* Execute the loop
|
||||
*/
|
||||
SPI_cursor_fetch(portal, true, 10);
|
||||
tuptab = SPI_tuptable;
|
||||
n = SPI_processed;
|
||||
|
||||
/*
|
||||
* If the query didn't return any rows, set the target to NULL and return
|
||||
* with FOUND = false.
|
||||
*/
|
||||
if (n == 0)
|
||||
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
|
||||
else
|
||||
found = true; /* processed at least one tuple */
|
||||
|
||||
/*
|
||||
* Now do the loop
|
||||
*/
|
||||
while (n > 0)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* Assign the tuple to the target
|
||||
*/
|
||||
exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
|
||||
|
||||
/*
|
||||
* Execute the statements
|
||||
*/
|
||||
rc = exec_stmts(estate, stmt->body);
|
||||
|
||||
if (rc != PLPGSQL_RC_OK)
|
||||
{
|
||||
if (rc == PLPGSQL_RC_EXIT)
|
||||
{
|
||||
if (estate->exitlabel == NULL)
|
||||
/* unlabelled exit, finish the current loop */
|
||||
rc = PLPGSQL_RC_OK;
|
||||
else if (stmt->label != NULL &&
|
||||
strcmp(stmt->label, estate->exitlabel) == 0)
|
||||
{
|
||||
/* labelled exit, matches the current stmt's label */
|
||||
estate->exitlabel = NULL;
|
||||
rc = PLPGSQL_RC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* otherwise, we processed a labelled exit that does not
|
||||
* match the current statement's label, if any: return
|
||||
* RC_EXIT so that the EXIT continues to recurse upward.
|
||||
*/
|
||||
}
|
||||
else if (rc == PLPGSQL_RC_CONTINUE)
|
||||
{
|
||||
if (estate->exitlabel == NULL)
|
||||
/* unlabelled continue, continue the current loop */
|
||||
continue;
|
||||
else if (stmt->label != NULL &&
|
||||
strcmp(stmt->label, estate->exitlabel) == 0)
|
||||
{
|
||||
/* labelled continue, matches the current stmt's label */
|
||||
estate->exitlabel = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* otherwise, we process a labelled continue that does not
|
||||
* match the current statement's label, so propagate
|
||||
* RC_CONTINUE upward in the stack.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* We're aborting the loop, so cleanup and set FOUND. (This
|
||||
* code should match the code after the loop.)
|
||||
*/
|
||||
SPI_freetuptable(tuptab);
|
||||
SPI_cursor_close(portal);
|
||||
exec_set_found(estate, found);
|
||||
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
SPI_freetuptable(tuptab);
|
||||
|
||||
/*
|
||||
* Fetch the next 50 tuples
|
||||
*/
|
||||
SPI_cursor_fetch(portal, true, 50);
|
||||
n = SPI_processed;
|
||||
tuptab = SPI_tuptable;
|
||||
}
|
||||
|
||||
/*
|
||||
* Release last group of tuples
|
||||
*/
|
||||
SPI_freetuptable(tuptab);
|
||||
rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true);
|
||||
|
||||
/*
|
||||
* Close the implicit cursor
|
||||
*/
|
||||
SPI_cursor_close(portal);
|
||||
|
||||
/*
|
||||
* Set the FOUND variable to indicate the result of executing the loop
|
||||
* (namely, whether we looped one or more times). This must be set here so
|
||||
* that it does not interfere with the value of the FOUND variable inside
|
||||
* the loop processing itself.
|
||||
*/
|
||||
exec_set_found(estate, found);
|
||||
|
||||
return PLPGSQL_RC_OK;
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@ -3038,16 +2914,14 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
|
||||
static int
|
||||
exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
||||
{
|
||||
PLpgSQL_var *curvar = NULL;
|
||||
PLpgSQL_var *curvar;
|
||||
char *curname = NULL;
|
||||
PLpgSQL_expr *query = NULL;
|
||||
PLpgSQL_expr *query;
|
||||
Portal portal;
|
||||
int i;
|
||||
Datum *values;
|
||||
char *nulls;
|
||||
bool isnull;
|
||||
|
||||
|
||||
/* ----------
|
||||
* Get the cursor variable and if it has an assigned name, check
|
||||
* that it's not in use currently.
|
||||
@ -3124,14 +2998,11 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
||||
pfree(querystr);
|
||||
SPI_freeplan(curplan);
|
||||
|
||||
/* ----------
|
||||
* Store the eventually assigned cursor name in the cursor variable
|
||||
* ----------
|
||||
/*
|
||||
* If cursor variable was NULL, store the generated portal name in it
|
||||
*/
|
||||
free_var(curvar);
|
||||
curvar->value = CStringGetTextDatum(portal->name);
|
||||
curvar->isnull = false;
|
||||
curvar->freeval = true;
|
||||
if (curname == NULL)
|
||||
assign_text_var(curvar, portal->name);
|
||||
|
||||
return PLPGSQL_RC_OK;
|
||||
}
|
||||
@ -3184,32 +3055,13 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
||||
exec_prepare_plan(estate, query, curvar->cursor_options);
|
||||
}
|
||||
|
||||
/* ----------
|
||||
* Here we go if we have a saved plan where we have to put
|
||||
* values into, either from an explicit cursor or from a
|
||||
* refcursor opened with OPEN ... FOR SELECT ...;
|
||||
* ----------
|
||||
/*
|
||||
* Now build up the values and nulls arguments for SPI_execute_plan()
|
||||
*/
|
||||
values = (Datum *) palloc(query->nparams * sizeof(Datum));
|
||||
nulls = (char *) palloc(query->nparams * sizeof(char));
|
||||
eval_expr_params(estate, query, &values, &nulls);
|
||||
|
||||
for (i = 0; i < query->nparams; i++)
|
||||
{
|
||||
PLpgSQL_datum *datum = estate->datums[query->params[i]];
|
||||
Oid paramtypeid;
|
||||
bool paramisnull;
|
||||
|
||||
exec_eval_datum(estate, datum, query->plan_argtypes[i],
|
||||
¶mtypeid, &values[i], ¶misnull);
|
||||
if (paramisnull)
|
||||
nulls[i] = 'n';
|
||||
else
|
||||
nulls[i] = ' ';
|
||||
}
|
||||
|
||||
/* ----------
|
||||
/*
|
||||
* Open the cursor
|
||||
* ----------
|
||||
*/
|
||||
portal = SPI_cursor_open(curname, query->plan, values, nulls,
|
||||
estate->readonly_func);
|
||||
@ -3217,20 +3069,17 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
||||
elog(ERROR, "could not open cursor: %s",
|
||||
SPI_result_code_string(SPI_result));
|
||||
|
||||
/*
|
||||
* If cursor variable was NULL, store the generated portal name in it
|
||||
*/
|
||||
if (curname == NULL)
|
||||
assign_text_var(curvar, portal->name);
|
||||
|
||||
pfree(values);
|
||||
pfree(nulls);
|
||||
if (curname)
|
||||
pfree(curname);
|
||||
|
||||
/* ----------
|
||||
* Store the eventually assigned portal name in the cursor variable
|
||||
* ----------
|
||||
*/
|
||||
free_var(curvar);
|
||||
curvar->value = CStringGetTextDatum(portal->name);
|
||||
curvar->isnull = false;
|
||||
curvar->freeval = true;
|
||||
|
||||
return PLPGSQL_RC_OK;
|
||||
}
|
||||
|
||||
@ -4082,7 +3931,6 @@ static int
|
||||
exec_run_select(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
|
||||
{
|
||||
int i;
|
||||
Datum *values;
|
||||
char *nulls;
|
||||
int rc;
|
||||
@ -4096,22 +3944,7 @@ exec_run_select(PLpgSQL_execstate *estate,
|
||||
/*
|
||||
* Now build up the values and nulls arguments for SPI_execute_plan()
|
||||
*/
|
||||
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
|
||||
nulls = (char *) palloc(expr->nparams * sizeof(char));
|
||||
|
||||
for (i = 0; i < expr->nparams; i++)
|
||||
{
|
||||
PLpgSQL_datum *datum = estate->datums[expr->params[i]];
|
||||
Oid paramtypeid;
|
||||
bool paramisnull;
|
||||
|
||||
exec_eval_datum(estate, datum, expr->plan_argtypes[i],
|
||||
¶mtypeid, &values[i], ¶misnull);
|
||||
if (paramisnull)
|
||||
nulls[i] = 'n';
|
||||
else
|
||||
nulls[i] = ' ';
|
||||
}
|
||||
eval_expr_params(estate, expr, &values, &nulls);
|
||||
|
||||
/*
|
||||
* If a portal was requested, put the query into the portal
|
||||
@ -4151,6 +3984,154 @@ exec_run_select(PLpgSQL_execstate *estate,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* exec_for_query --- execute body of FOR loop for each row from a portal
|
||||
*
|
||||
* Used by exec_stmt_fors, exec_stmt_forc and exec_stmt_dynfors
|
||||
*/
|
||||
static int
|
||||
exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
|
||||
Portal portal, bool prefetch_ok)
|
||||
{
|
||||
PLpgSQL_rec *rec = NULL;
|
||||
PLpgSQL_row *row = NULL;
|
||||
SPITupleTable *tuptab;
|
||||
bool found = false;
|
||||
int rc = PLPGSQL_RC_OK;
|
||||
int n;
|
||||
|
||||
/*
|
||||
* Determine if we assign to a record or a row
|
||||
*/
|
||||
if (stmt->rec != NULL)
|
||||
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||
else if (stmt->row != NULL)
|
||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||
else
|
||||
elog(ERROR, "unsupported target");
|
||||
|
||||
/*
|
||||
* Fetch the initial tuple(s). If prefetching is allowed then we grab
|
||||
* a few more rows to avoid multiple trips through executor startup
|
||||
* overhead.
|
||||
*/
|
||||
SPI_cursor_fetch(portal, true, prefetch_ok ? 10 : 1);
|
||||
tuptab = SPI_tuptable;
|
||||
n = SPI_processed;
|
||||
|
||||
/*
|
||||
* If the query didn't return any rows, set the target to NULL and
|
||||
* fall through with found = false.
|
||||
*/
|
||||
if (n <= 0)
|
||||
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
|
||||
else
|
||||
found = true; /* processed at least one tuple */
|
||||
|
||||
/*
|
||||
* Now do the loop
|
||||
*/
|
||||
while (n > 0)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
/*
|
||||
* Assign the tuple to the target
|
||||
*/
|
||||
exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
|
||||
|
||||
/*
|
||||
* Execute the statements
|
||||
*/
|
||||
rc = exec_stmts(estate, stmt->body);
|
||||
|
||||
if (rc != PLPGSQL_RC_OK)
|
||||
{
|
||||
if (rc == PLPGSQL_RC_EXIT)
|
||||
{
|
||||
if (estate->exitlabel == NULL)
|
||||
{
|
||||
/* unlabelled exit, so exit the current loop */
|
||||
rc = PLPGSQL_RC_OK;
|
||||
}
|
||||
else if (stmt->label != NULL &&
|
||||
strcmp(stmt->label, estate->exitlabel) == 0)
|
||||
{
|
||||
/* label matches this loop, so exit loop */
|
||||
estate->exitlabel = NULL;
|
||||
rc = PLPGSQL_RC_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* otherwise, we processed a labelled exit that does not
|
||||
* match the current statement's label, if any; return
|
||||
* RC_EXIT so that the EXIT continues to recurse upward.
|
||||
*/
|
||||
}
|
||||
else if (rc == PLPGSQL_RC_CONTINUE)
|
||||
{
|
||||
if (estate->exitlabel == NULL)
|
||||
{
|
||||
/* unlabelled continue, so re-run the current loop */
|
||||
rc = PLPGSQL_RC_OK;
|
||||
continue;
|
||||
}
|
||||
else if (stmt->label != NULL &&
|
||||
strcmp(stmt->label, estate->exitlabel) == 0)
|
||||
{
|
||||
/* label matches this loop, so re-run loop */
|
||||
estate->exitlabel = NULL;
|
||||
rc = PLPGSQL_RC_OK;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* otherwise, we process a labelled continue that does not
|
||||
* match the current statement's label, if any; return
|
||||
* RC_CONTINUE so that the CONTINUE will propagate up the
|
||||
* stack.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* We're aborting the loop. Need a goto to get out of two
|
||||
* levels of loop...
|
||||
*/
|
||||
goto loop_exit;
|
||||
}
|
||||
}
|
||||
|
||||
SPI_freetuptable(tuptab);
|
||||
|
||||
/*
|
||||
* Fetch more tuples. If prefetching is allowed, grab 50 at a time.
|
||||
*/
|
||||
SPI_cursor_fetch(portal, true, prefetch_ok ? 50 : 1);
|
||||
tuptab = SPI_tuptable;
|
||||
n = SPI_processed;
|
||||
}
|
||||
|
||||
loop_exit:
|
||||
|
||||
/*
|
||||
* Release last group of tuples (if any)
|
||||
*/
|
||||
SPI_freetuptable(tuptab);
|
||||
|
||||
/*
|
||||
* Set the FOUND variable to indicate the result of executing the loop
|
||||
* (namely, whether we looped one or more times). This must be set last so
|
||||
* that it does not interfere with the value of the FOUND variable inside
|
||||
* the loop processing itself.
|
||||
*/
|
||||
exec_set_found(estate, found);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* ----------
|
||||
* exec_eval_simple_expr - Evaluate a simple expression returning
|
||||
* a Datum by directly calling ExecEvalExpr().
|
||||
@ -4317,6 +4298,36 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Build up the values and nulls arguments for SPI_execute_plan()
|
||||
*/
|
||||
static void
|
||||
eval_expr_params(PLpgSQL_execstate *estate,
|
||||
PLpgSQL_expr *expr, Datum **p_values, char **p_nulls)
|
||||
{
|
||||
Datum *values;
|
||||
char *nulls;
|
||||
int i;
|
||||
|
||||
*p_values = values = (Datum *) palloc(expr->nparams * sizeof(Datum));
|
||||
*p_nulls = nulls = (char *) palloc(expr->nparams * sizeof(char));
|
||||
|
||||
for (i = 0; i < expr->nparams; i++)
|
||||
{
|
||||
PLpgSQL_datum *datum = estate->datums[expr->params[i]];
|
||||
Oid paramtypeid;
|
||||
bool paramisnull;
|
||||
|
||||
exec_eval_datum(estate, datum, expr->plan_argtypes[i],
|
||||
¶mtypeid, &values[i], ¶misnull);
|
||||
if (paramisnull)
|
||||
nulls[i] = 'n';
|
||||
else
|
||||
nulls[i] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------
|
||||
* exec_move_row Move one tuple's values into a record or row
|
||||
* ----------
|
||||
@ -5109,6 +5120,18 @@ free_var(PLpgSQL_var *var)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* free old value of a text variable and assign new value from C string
|
||||
*/
|
||||
static void
|
||||
assign_text_var(PLpgSQL_var *var, const char *str)
|
||||
{
|
||||
free_var(var);
|
||||
var->value = CStringGetTextDatum(str);
|
||||
var->isnull = false;
|
||||
var->freeval = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* exec_eval_using_params --- evaluate params of USING clause
|
||||
*/
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.68 2008/04/01 03:51:09 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.69 2008/04/06 23:43:29 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -482,6 +482,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
|
||||
return _("FOR with integer loop variable");
|
||||
case PLPGSQL_STMT_FORS:
|
||||
return _("FOR over SELECT rows");
|
||||
case PLPGSQL_STMT_FORC:
|
||||
return _("FOR over cursor");
|
||||
case PLPGSQL_STMT_EXIT:
|
||||
return "EXIT";
|
||||
case PLPGSQL_STMT_RETURN:
|
||||
@ -528,6 +530,7 @@ static void dump_loop(PLpgSQL_stmt_loop *stmt);
|
||||
static void dump_while(PLpgSQL_stmt_while *stmt);
|
||||
static void dump_fori(PLpgSQL_stmt_fori *stmt);
|
||||
static void dump_fors(PLpgSQL_stmt_fors *stmt);
|
||||
static void dump_forc(PLpgSQL_stmt_forc *stmt);
|
||||
static void dump_exit(PLpgSQL_stmt_exit *stmt);
|
||||
static void dump_return(PLpgSQL_stmt_return *stmt);
|
||||
static void dump_return_next(PLpgSQL_stmt_return_next *stmt);
|
||||
@ -581,6 +584,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
|
||||
case PLPGSQL_STMT_FORS:
|
||||
dump_fors((PLpgSQL_stmt_fors *) stmt);
|
||||
break;
|
||||
case PLPGSQL_STMT_FORC:
|
||||
dump_forc((PLpgSQL_stmt_forc *) stmt);
|
||||
break;
|
||||
case PLPGSQL_STMT_EXIT:
|
||||
dump_exit((PLpgSQL_stmt_exit *) stmt);
|
||||
break;
|
||||
@ -775,6 +781,29 @@ dump_fors(PLpgSQL_stmt_fors *stmt)
|
||||
printf(" ENDFORS\n");
|
||||
}
|
||||
|
||||
static void
|
||||
dump_forc(PLpgSQL_stmt_forc *stmt)
|
||||
{
|
||||
dump_ind();
|
||||
printf("FORC %s ", stmt->rec->refname);
|
||||
printf("curvar=%d\n", stmt->curvar);
|
||||
|
||||
dump_indent += 2;
|
||||
if (stmt->argquery != NULL)
|
||||
{
|
||||
dump_ind();
|
||||
printf(" arguments = ");
|
||||
dump_expr(stmt->argquery);
|
||||
printf("\n");
|
||||
}
|
||||
dump_indent -= 2;
|
||||
|
||||
dump_stmts(stmt->body);
|
||||
|
||||
dump_ind();
|
||||
printf(" ENDFORC\n");
|
||||
}
|
||||
|
||||
static void
|
||||
dump_open(PLpgSQL_stmt_open *stmt)
|
||||
{
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.96 2008/04/01 03:51:09 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.97 2008/04/06 23:43:29 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -80,6 +80,7 @@ enum
|
||||
PLPGSQL_STMT_WHILE,
|
||||
PLPGSQL_STMT_FORI,
|
||||
PLPGSQL_STMT_FORS,
|
||||
PLPGSQL_STMT_FORC,
|
||||
PLPGSQL_STMT_EXIT,
|
||||
PLPGSQL_STMT_RETURN,
|
||||
PLPGSQL_STMT_RETURN_NEXT,
|
||||
@ -409,6 +410,21 @@ typedef struct
|
||||
} PLpgSQL_stmt_fori;
|
||||
|
||||
|
||||
/*
|
||||
* PLpgSQL_stmt_forq represents a FOR statement running over a SQL query.
|
||||
* It is the common supertype of PLpgSQL_stmt_fors, PLpgSQL_stmt_forc
|
||||
* and PLpgSQL_dynfors.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int cmd_type;
|
||||
int lineno;
|
||||
char *label;
|
||||
PLpgSQL_rec *rec;
|
||||
PLpgSQL_row *row;
|
||||
List *body; /* List of statements */
|
||||
} PLpgSQL_stmt_forq;
|
||||
|
||||
typedef struct
|
||||
{ /* FOR statement running over SELECT */
|
||||
int cmd_type;
|
||||
@ -416,10 +432,23 @@ typedef struct
|
||||
char *label;
|
||||
PLpgSQL_rec *rec;
|
||||
PLpgSQL_row *row;
|
||||
PLpgSQL_expr *query;
|
||||
List *body; /* List of statements */
|
||||
/* end of fields that must match PLpgSQL_stmt_forq */
|
||||
PLpgSQL_expr *query;
|
||||
} PLpgSQL_stmt_fors;
|
||||
|
||||
typedef struct
|
||||
{ /* FOR statement running over cursor */
|
||||
int cmd_type;
|
||||
int lineno;
|
||||
char *label;
|
||||
PLpgSQL_rec *rec;
|
||||
PLpgSQL_row *row;
|
||||
List *body; /* List of statements */
|
||||
/* end of fields that must match PLpgSQL_stmt_forq */
|
||||
int curvar;
|
||||
PLpgSQL_expr *argquery; /* cursor arguments if any */
|
||||
} PLpgSQL_stmt_forc;
|
||||
|
||||
typedef struct
|
||||
{ /* FOR statement running over EXECUTE */
|
||||
@ -428,8 +457,9 @@ typedef struct
|
||||
char *label;
|
||||
PLpgSQL_rec *rec;
|
||||
PLpgSQL_row *row;
|
||||
PLpgSQL_expr *query;
|
||||
List *body; /* List of statements */
|
||||
/* end of fields that must match PLpgSQL_stmt_forq */
|
||||
PLpgSQL_expr *query;
|
||||
List *params; /* USING expressions */
|
||||
} PLpgSQL_stmt_dynfors;
|
||||
|
||||
@ -738,6 +768,8 @@ extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod);
|
||||
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
|
||||
PLpgSQL_type *dtype,
|
||||
bool add2namespace);
|
||||
extern PLpgSQL_rec *plpgsql_build_record(const char *refname, int lineno,
|
||||
bool add2namespace);
|
||||
extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
|
||||
extern void plpgsql_adddatum(PLpgSQL_datum *new);
|
||||
extern int plpgsql_add_initdatums(int **varnos);
|
||||
|
@ -3151,3 +3151,119 @@ NOTICE: 6
|
||||
26
|
||||
(1 row)
|
||||
|
||||
-- test FOR-over-cursor
|
||||
create or replace function forc01() returns void as $$
|
||||
declare
|
||||
c cursor(r1 integer, r2 integer)
|
||||
for select * from generate_series(r1,r2) i;
|
||||
c2 cursor
|
||||
for select * from generate_series(41,43) i;
|
||||
begin
|
||||
for r in c(5,7) loop
|
||||
raise notice '% from %', r.i, c;
|
||||
end loop;
|
||||
-- again, to test if cursor was closed properly
|
||||
for r in c(9,10) loop
|
||||
raise notice '% from %', r.i, c;
|
||||
end loop;
|
||||
-- and test a parameterless cursor
|
||||
for r in c2 loop
|
||||
raise notice '% from %', r.i, c2;
|
||||
end loop;
|
||||
-- and try it with a hand-assigned name
|
||||
raise notice 'after loop, c2 = %', c2;
|
||||
c2 := 'special_name';
|
||||
for r in c2 loop
|
||||
raise notice '% from %', r.i, c2;
|
||||
end loop;
|
||||
raise notice 'after loop, c2 = %', c2;
|
||||
-- and try it with a generated name
|
||||
-- (which we can't show in the output because it's variable)
|
||||
c2 := null;
|
||||
for r in c2 loop
|
||||
raise notice '%', r.i;
|
||||
end loop;
|
||||
raise notice 'after loop, c2 = %', c2;
|
||||
return;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
select forc01();
|
||||
NOTICE: 5 from c
|
||||
NOTICE: 6 from c
|
||||
NOTICE: 7 from c
|
||||
NOTICE: 9 from c
|
||||
NOTICE: 10 from c
|
||||
NOTICE: 41 from c2
|
||||
NOTICE: 42 from c2
|
||||
NOTICE: 43 from c2
|
||||
NOTICE: after loop, c2 = c2
|
||||
NOTICE: 41 from special_name
|
||||
NOTICE: 42 from special_name
|
||||
NOTICE: 43 from special_name
|
||||
NOTICE: after loop, c2 = special_name
|
||||
NOTICE: 41
|
||||
NOTICE: 42
|
||||
NOTICE: 43
|
||||
NOTICE: after loop, c2 = <NULL>
|
||||
forc01
|
||||
--------
|
||||
|
||||
(1 row)
|
||||
|
||||
-- try updating the cursor's current row
|
||||
create temp table forc_test as
|
||||
select n as i, n as j from generate_series(1,10) n;
|
||||
create or replace function forc01() returns void as $$
|
||||
declare
|
||||
c cursor for select * from forc_test;
|
||||
begin
|
||||
for r in c loop
|
||||
raise notice '%, %', r.i, r.j;
|
||||
update forc_test set i = i * 100, j = r.j * 2 where current of c;
|
||||
end loop;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
select forc01();
|
||||
NOTICE: 1, 1
|
||||
NOTICE: 2, 2
|
||||
NOTICE: 3, 3
|
||||
NOTICE: 4, 4
|
||||
NOTICE: 5, 5
|
||||
NOTICE: 6, 6
|
||||
NOTICE: 7, 7
|
||||
NOTICE: 8, 8
|
||||
NOTICE: 9, 9
|
||||
NOTICE: 10, 10
|
||||
forc01
|
||||
--------
|
||||
|
||||
(1 row)
|
||||
|
||||
select * from forc_test;
|
||||
i | j
|
||||
------+----
|
||||
100 | 2
|
||||
200 | 4
|
||||
300 | 6
|
||||
400 | 8
|
||||
500 | 10
|
||||
600 | 12
|
||||
700 | 14
|
||||
800 | 16
|
||||
900 | 18
|
||||
1000 | 20
|
||||
(10 rows)
|
||||
|
||||
drop function forc01();
|
||||
-- fail because cursor has no query bound to it
|
||||
create or replace function forc_bad() returns void as $$
|
||||
declare
|
||||
c refcursor;
|
||||
begin
|
||||
for r in c loop
|
||||
raise notice '%', r.i;
|
||||
end loop;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
ERROR: cursor FOR loop must use a bound cursor variable
|
||||
CONTEXT: compile of PL/pgSQL function "forc_bad" near line 4
|
||||
|
@ -2595,3 +2595,77 @@ end
|
||||
$$ language plpgsql;
|
||||
|
||||
select exc_using(5, 'foobar');
|
||||
|
||||
-- test FOR-over-cursor
|
||||
|
||||
create or replace function forc01() returns void as $$
|
||||
declare
|
||||
c cursor(r1 integer, r2 integer)
|
||||
for select * from generate_series(r1,r2) i;
|
||||
c2 cursor
|
||||
for select * from generate_series(41,43) i;
|
||||
begin
|
||||
for r in c(5,7) loop
|
||||
raise notice '% from %', r.i, c;
|
||||
end loop;
|
||||
-- again, to test if cursor was closed properly
|
||||
for r in c(9,10) loop
|
||||
raise notice '% from %', r.i, c;
|
||||
end loop;
|
||||
-- and test a parameterless cursor
|
||||
for r in c2 loop
|
||||
raise notice '% from %', r.i, c2;
|
||||
end loop;
|
||||
-- and try it with a hand-assigned name
|
||||
raise notice 'after loop, c2 = %', c2;
|
||||
c2 := 'special_name';
|
||||
for r in c2 loop
|
||||
raise notice '% from %', r.i, c2;
|
||||
end loop;
|
||||
raise notice 'after loop, c2 = %', c2;
|
||||
-- and try it with a generated name
|
||||
-- (which we can't show in the output because it's variable)
|
||||
c2 := null;
|
||||
for r in c2 loop
|
||||
raise notice '%', r.i;
|
||||
end loop;
|
||||
raise notice 'after loop, c2 = %', c2;
|
||||
return;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
select forc01();
|
||||
|
||||
-- try updating the cursor's current row
|
||||
|
||||
create temp table forc_test as
|
||||
select n as i, n as j from generate_series(1,10) n;
|
||||
|
||||
create or replace function forc01() returns void as $$
|
||||
declare
|
||||
c cursor for select * from forc_test;
|
||||
begin
|
||||
for r in c loop
|
||||
raise notice '%, %', r.i, r.j;
|
||||
update forc_test set i = i * 100, j = r.j * 2 where current of c;
|
||||
end loop;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
select forc01();
|
||||
|
||||
select * from forc_test;
|
||||
|
||||
drop function forc01();
|
||||
|
||||
-- fail because cursor has no query bound to it
|
||||
|
||||
create or replace function forc_bad() returns void as $$
|
||||
declare
|
||||
c refcursor;
|
||||
begin
|
||||
for r in c loop
|
||||
raise notice '%', r.i;
|
||||
end loop;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
Loading…
x
Reference in New Issue
Block a user