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">
|
<chapter id="plpgsql">
|
||||||
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
|
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
|
||||||
@ -286,9 +286,11 @@ $$ LANGUAGE plpgsql;
|
|||||||
<para>
|
<para>
|
||||||
All variables used in a block must be declared in the
|
All variables used in a block must be declared in the
|
||||||
declarations section of the block.
|
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
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -1317,10 +1319,11 @@ GET DIAGNOSTICS integer_var = ROW_COUNT;
|
|||||||
<para>
|
<para>
|
||||||
A <command>FOR</> statement sets <literal>FOUND</literal> true
|
A <command>FOR</> statement sets <literal>FOUND</literal> true
|
||||||
if it iterates one or more times, else false. This applies to
|
if it iterates one or more times, else false. This applies to
|
||||||
all three variants of the <command>FOR</> statement (integer
|
all four variants of the <command>FOR</> statement (integer
|
||||||
<command>FOR</> loops, record-set <command>FOR</> loops, and
|
<command>FOR</> loops, record-set <command>FOR</> loops,
|
||||||
dynamic record-set <command>FOR</>
|
dynamic record-set <command>FOR</> loops, and cursor
|
||||||
loops). <literal>FOUND</literal> is set this way when the
|
<command>FOR</> loops).
|
||||||
|
<literal>FOUND</literal> is set this way when the
|
||||||
<command>FOR</> loop exits; inside the execution of the loop,
|
<command>FOR</> loop exits; inside the execution of the loop,
|
||||||
<literal>FOUND</literal> is not modified by the
|
<literal>FOUND</literal> is not modified by the
|
||||||
<command>FOR</> statement, although it might be changed by the
|
<command>FOR</> statement, although it might be changed by the
|
||||||
@ -2057,6 +2060,12 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
|
|||||||
As with <command>EXECUTE</command>, parameter values can be inserted
|
As with <command>EXECUTE</command>, parameter values can be inserted
|
||||||
into the dynamic command via <literal>USING</>.
|
into the dynamic command via <literal>USING</>.
|
||||||
</para>
|
</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>
|
||||||
|
|
||||||
<sect2 id="plpgsql-error-trapping">
|
<sect2 id="plpgsql-error-trapping">
|
||||||
@ -2293,6 +2302,14 @@ DECLARE
|
|||||||
cursor variables while the third uses a bound cursor variable.
|
cursor variables while the third uses a bound cursor variable.
|
||||||
</para>
|
</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>
|
<sect3>
|
||||||
<title><command>OPEN FOR</command> <replaceable>query</replaceable></title>
|
<title><command>OPEN FOR</command> <replaceable>query</replaceable></title>
|
||||||
|
|
||||||
@ -2676,6 +2693,36 @@ COMMIT;
|
|||||||
</para>
|
</para>
|
||||||
</sect3>
|
</sect3>
|
||||||
</sect2>
|
</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>
|
||||||
|
|
||||||
<sect1 id="plpgsql-errors-and-messages">
|
<sect1 id="plpgsql-errors-and-messages">
|
||||||
@ -3796,14 +3843,6 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</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>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
In <productname>PostgreSQL</> the function body must be written as
|
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>
|
</para>
|
||||||
</listitem>
|
</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>
|
</itemizedlist>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
@ -3939,8 +3995,7 @@ $$ LANGUAGE plpgsql;
|
|||||||
The following procedure grabs rows from a
|
The following procedure grabs rows from a
|
||||||
<command>SELECT</command> statement and builds a large function
|
<command>SELECT</command> statement and builds a large function
|
||||||
with the results in <literal>IF</literal> statements, for the
|
with the results in <literal>IF</literal> statements, for the
|
||||||
sake of efficiency. Notice particularly the differences in the
|
sake of efficiency.
|
||||||
cursor and the <literal>FOR</literal> loop.
|
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -3950,7 +4005,6 @@ CREATE OR REPLACE PROCEDURE cs_update_referrer_type_proc IS
|
|||||||
CURSOR referrer_keys IS
|
CURSOR referrer_keys IS
|
||||||
SELECT * FROM cs_referrer_keys
|
SELECT * FROM cs_referrer_keys
|
||||||
ORDER BY try_order;
|
ORDER BY try_order;
|
||||||
|
|
||||||
func_cmd VARCHAR(4000);
|
func_cmd VARCHAR(4000);
|
||||||
BEGIN
|
BEGIN
|
||||||
func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR,
|
func_cmd := 'CREATE OR REPLACE FUNCTION cs_find_referrer_type(v_host IN VARCHAR,
|
||||||
@ -3978,16 +4032,15 @@ show errors;
|
|||||||
<programlisting>
|
<programlisting>
|
||||||
CREATE OR REPLACE FUNCTION cs_update_referrer_type_proc() RETURNS void AS $func$
|
CREATE OR REPLACE FUNCTION cs_update_referrer_type_proc() RETURNS void AS $func$
|
||||||
DECLARE
|
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_body text;
|
||||||
func_cmd text;
|
func_cmd text;
|
||||||
BEGIN
|
BEGIN
|
||||||
func_body := 'BEGIN';
|
func_body := 'BEGIN';
|
||||||
|
|
||||||
-- Notice how we scan through the results of a query in a FOR loop
|
FOR referrer_key IN referrer_keys LOOP
|
||||||
-- using the FOR <record> construct.
|
|
||||||
|
|
||||||
FOR referrer_key IN SELECT * FROM cs_referrer_keys ORDER BY try_order LOOP
|
|
||||||
func_body := func_body ||
|
func_body := func_body ||
|
||||||
' IF v_' || referrer_key.kind
|
' IF v_' || referrer_key.kind
|
||||||
|| ' LIKE ' || quote_literal(referrer_key.key_string)
|
|| ' LIKE ' || quote_literal(referrer_key.key_string)
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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 char *check_label(const char *yytxt);
|
||||||
static void check_labels(const char *start_label,
|
static void check_labels(const char *start_label,
|
||||||
const char *end_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;
|
new->body = $4.stmts;
|
||||||
$$ = (PLpgSQL_stmt *) new;
|
$$ = (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
|
else
|
||||||
{
|
{
|
||||||
PLpgSQL_stmt_dynfors *new;
|
PLpgSQL_stmt_forq *new;
|
||||||
|
|
||||||
Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
|
Assert($3->cmd_type == PLPGSQL_STMT_FORS ||
|
||||||
new = (PLpgSQL_stmt_dynfors *) $3;
|
$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->label = $1;
|
||||||
new->body = $4.stmts;
|
new->body = $4.stmts;
|
||||||
$$ = (PLpgSQL_stmt *) new;
|
$$ = (PLpgSQL_stmt *) new;
|
||||||
@ -892,9 +888,9 @@ for_control :
|
|||||||
{
|
{
|
||||||
int tok = yylex();
|
int tok = yylex();
|
||||||
|
|
||||||
/* Simple case: EXECUTE is a dynamic FOR loop */
|
|
||||||
if (tok == K_EXECUTE)
|
if (tok == K_EXECUTE)
|
||||||
{
|
{
|
||||||
|
/* EXECUTE means it's a dynamic FOR loop */
|
||||||
PLpgSQL_stmt_dynfors *new;
|
PLpgSQL_stmt_dynfors *new;
|
||||||
PLpgSQL_expr *expr;
|
PLpgSQL_expr *expr;
|
||||||
int term;
|
int term;
|
||||||
@ -942,6 +938,47 @@ for_control :
|
|||||||
|
|
||||||
$$ = (PLpgSQL_stmt *) new;
|
$$ = (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
|
else
|
||||||
{
|
{
|
||||||
PLpgSQL_expr *expr1;
|
PLpgSQL_expr *expr1;
|
||||||
@ -1412,81 +1449,8 @@ stmt_open : K_OPEN lno cursor_variable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ($3->cursor_explicit_argrow >= 0)
|
/* predefined cursor query, so read args */
|
||||||
{
|
new->argquery = read_cursor_args($3, ';', ";");
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$$ = (PLpgSQL_stmt *)new;
|
$$ = (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: */
|
/* Needed to avoid conflict between different prefix settings: */
|
||||||
#undef yylex
|
#undef yylex
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.")));
|
errhint("You probably want to use TG_NARGS and TG_ARGV instead.")));
|
||||||
|
|
||||||
/* Add the record for referencing NEW */
|
/* Add the record for referencing NEW */
|
||||||
rec = palloc0(sizeof(PLpgSQL_rec));
|
rec = plpgsql_build_record("new", 0, true);
|
||||||
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);
|
|
||||||
function->new_varno = rec->recno;
|
function->new_varno = rec->recno;
|
||||||
|
|
||||||
/* Add the record for referencing OLD */
|
/* Add the record for referencing OLD */
|
||||||
rec = palloc0(sizeof(PLpgSQL_rec));
|
rec = plpgsql_build_record("old", 0, true);
|
||||||
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);
|
|
||||||
function->old_varno = rec->recno;
|
function->old_varno = rec->recno;
|
||||||
|
|
||||||
/* Add the variable tg_name */
|
/* Add the variable tg_name */
|
||||||
@ -1481,21 +1467,10 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
|
|||||||
}
|
}
|
||||||
case PLPGSQL_TTYPE_REC:
|
case PLPGSQL_TTYPE_REC:
|
||||||
{
|
{
|
||||||
/*
|
/* "record" type -- build a record variable */
|
||||||
* "record" type -- build a variable-contents record variable
|
|
||||||
*/
|
|
||||||
PLpgSQL_rec *rec;
|
PLpgSQL_rec *rec;
|
||||||
|
|
||||||
rec = palloc0(sizeof(PLpgSQL_rec));
|
rec = plpgsql_build_record(refname, lineno, add2namespace);
|
||||||
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);
|
|
||||||
result = (PLpgSQL_variable *) rec;
|
result = (PLpgSQL_variable *) rec;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1515,6 +1490,28 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
|
|||||||
return result;
|
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.
|
* Build a row-variable data structure given the pg_class OID.
|
||||||
*/
|
*/
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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);
|
PLpgSQL_stmt_fori *stmt);
|
||||||
static int exec_stmt_fors(PLpgSQL_execstate *estate,
|
static int exec_stmt_fors(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_stmt_fors *stmt);
|
PLpgSQL_stmt_fors *stmt);
|
||||||
|
static int exec_stmt_forc(PLpgSQL_execstate *estate,
|
||||||
|
PLpgSQL_stmt_forc *stmt);
|
||||||
static int exec_stmt_open(PLpgSQL_execstate *estate,
|
static int exec_stmt_open(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_stmt_open *stmt);
|
PLpgSQL_stmt_open *stmt);
|
||||||
static int exec_stmt_fetch(PLpgSQL_execstate *estate,
|
static int exec_stmt_fetch(PLpgSQL_execstate *estate,
|
||||||
@ -165,6 +167,10 @@ static Datum exec_eval_expr(PLpgSQL_execstate *estate,
|
|||||||
Oid *rettype);
|
Oid *rettype);
|
||||||
static int exec_run_select(PLpgSQL_execstate *estate,
|
static int exec_run_select(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_expr *expr, long maxtuples, Portal *portalP);
|
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,
|
static void exec_move_row(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_rec *rec,
|
PLpgSQL_rec *rec,
|
||||||
PLpgSQL_row *row,
|
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 exec_set_found(PLpgSQL_execstate *estate, bool state);
|
||||||
static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
|
static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
|
||||||
static void free_var(PLpgSQL_var *var);
|
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,
|
static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
|
||||||
List *params);
|
List *params);
|
||||||
static void free_params_data(PreparedParamsData *ppd);
|
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 *)
|
state_var = (PLpgSQL_var *)
|
||||||
estate->datums[block->exceptions->sqlstate_varno];
|
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 *)
|
errm_var = (PLpgSQL_var *)
|
||||||
estate->datums[block->exceptions->sqlerrm_varno];
|
estate->datums[block->exceptions->sqlerrm_varno];
|
||||||
errm_var->value = CStringGetTextDatum(edata->message);
|
|
||||||
errm_var->freeval = true;
|
assign_text_var(state_var,
|
||||||
errm_var->isnull = false;
|
unpack_sql_state(edata->sqlerrcode));
|
||||||
|
assign_text_var(errm_var, edata->message);
|
||||||
|
|
||||||
estate->err_text = NULL;
|
estate->err_text = NULL;
|
||||||
|
|
||||||
@ -1100,8 +1104,10 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
|
|||||||
|
|
||||||
free_var(state_var);
|
free_var(state_var);
|
||||||
state_var->value = (Datum) 0;
|
state_var->value = (Datum) 0;
|
||||||
|
state_var->isnull = true;
|
||||||
free_var(errm_var);
|
free_var(errm_var);
|
||||||
errm_var->value = (Datum) 0;
|
errm_var->value = (Datum) 0;
|
||||||
|
errm_var->isnull = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1246,6 +1252,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
|
|||||||
rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt);
|
rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PLPGSQL_STMT_FORC:
|
||||||
|
rc = exec_stmt_forc(estate, (PLpgSQL_stmt_forc *) stmt);
|
||||||
|
break;
|
||||||
|
|
||||||
case PLPGSQL_STMT_EXIT:
|
case PLPGSQL_STMT_EXIT:
|
||||||
rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt);
|
rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt);
|
||||||
break;
|
break;
|
||||||
@ -1724,145 +1734,149 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
|
|||||||
static int
|
static int
|
||||||
exec_stmt_fors(PLpgSQL_execstate *estate, PLpgSQL_stmt_fors *stmt)
|
exec_stmt_fors(PLpgSQL_execstate *estate, PLpgSQL_stmt_fors *stmt)
|
||||||
{
|
{
|
||||||
PLpgSQL_rec *rec = NULL;
|
|
||||||
PLpgSQL_row *row = NULL;
|
|
||||||
SPITupleTable *tuptab;
|
|
||||||
Portal portal;
|
Portal portal;
|
||||||
bool found = false;
|
int rc;
|
||||||
int rc = PLPGSQL_RC_OK;
|
|
||||||
int i;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine if we assign to a record or a row
|
* Open the implicit cursor for the statement using exec_run_select
|
||||||
*/
|
|
||||||
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.
|
|
||||||
*/
|
*/
|
||||||
exec_run_select(estate, stmt->query, 0, &portal);
|
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
|
* Execute the loop
|
||||||
* with FOUND = false.
|
|
||||||
*/
|
*/
|
||||||
if (n == 0)
|
rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true);
|
||||||
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);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Close the implicit cursor
|
* Close the implicit cursor
|
||||||
*/
|
*/
|
||||||
SPI_cursor_close(portal);
|
SPI_cursor_close(portal);
|
||||||
|
|
||||||
/*
|
return rc;
|
||||||
* 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_stmt_forc Execute a loop for each row from a cursor.
|
||||||
|
* ----------
|
||||||
*/
|
*/
|
||||||
exec_set_found(estate, found);
|
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.
|
||||||
|
* ----------
|
||||||
|
*/
|
||||||
|
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;
|
return rc;
|
||||||
}
|
}
|
||||||
@ -2470,7 +2484,6 @@ static int
|
|||||||
exec_stmt_execsql(PLpgSQL_execstate *estate,
|
exec_stmt_execsql(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_stmt_execsql *stmt)
|
PLpgSQL_stmt_execsql *stmt)
|
||||||
{
|
{
|
||||||
int i;
|
|
||||||
Datum *values;
|
Datum *values;
|
||||||
char *nulls;
|
char *nulls;
|
||||||
long tcount;
|
long tcount;
|
||||||
@ -2511,22 +2524,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
|
|||||||
/*
|
/*
|
||||||
* Now build up the values and nulls arguments for SPI_execute_plan()
|
* Now build up the values and nulls arguments for SPI_execute_plan()
|
||||||
*/
|
*/
|
||||||
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
|
eval_expr_params(estate, expr, &values, &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] = ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we have INTO, then we only need one row back ... but if we have INTO
|
* 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;
|
bool isnull;
|
||||||
Oid restype;
|
Oid restype;
|
||||||
char *querystr;
|
char *querystr;
|
||||||
PLpgSQL_rec *rec = NULL;
|
|
||||||
PLpgSQL_row *row = NULL;
|
|
||||||
SPITupleTable *tuptab;
|
|
||||||
int n;
|
|
||||||
Portal portal;
|
Portal portal;
|
||||||
bool found = false;
|
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");
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Evaluate the string expression after the EXECUTE keyword. It's result
|
* 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);
|
pfree(querystr);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fetch the initial 10 tuples
|
* Execute the loop
|
||||||
*/
|
*/
|
||||||
SPI_cursor_fetch(portal, true, 10);
|
rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true);
|
||||||
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);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Close the implicit cursor
|
* Close the implicit cursor
|
||||||
*/
|
*/
|
||||||
SPI_cursor_close(portal);
|
SPI_cursor_close(portal);
|
||||||
|
|
||||||
/*
|
return rc;
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3038,16 +2914,14 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
|
|||||||
static int
|
static int
|
||||||
exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
||||||
{
|
{
|
||||||
PLpgSQL_var *curvar = NULL;
|
PLpgSQL_var *curvar;
|
||||||
char *curname = NULL;
|
char *curname = NULL;
|
||||||
PLpgSQL_expr *query = NULL;
|
PLpgSQL_expr *query;
|
||||||
Portal portal;
|
Portal portal;
|
||||||
int i;
|
|
||||||
Datum *values;
|
Datum *values;
|
||||||
char *nulls;
|
char *nulls;
|
||||||
bool isnull;
|
bool isnull;
|
||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* Get the cursor variable and if it has an assigned name, check
|
* Get the cursor variable and if it has an assigned name, check
|
||||||
* that it's not in use currently.
|
* that it's not in use currently.
|
||||||
@ -3124,14 +2998,11 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
|||||||
pfree(querystr);
|
pfree(querystr);
|
||||||
SPI_freeplan(curplan);
|
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);
|
if (curname == NULL)
|
||||||
curvar->value = CStringGetTextDatum(portal->name);
|
assign_text_var(curvar, portal->name);
|
||||||
curvar->isnull = false;
|
|
||||||
curvar->freeval = true;
|
|
||||||
|
|
||||||
return PLPGSQL_RC_OK;
|
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);
|
exec_prepare_plan(estate, query, curvar->cursor_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------
|
/*
|
||||||
* Here we go if we have a saved plan where we have to put
|
* Now build up the values and nulls arguments for SPI_execute_plan()
|
||||||
* values into, either from an explicit cursor or from a
|
|
||||||
* refcursor opened with OPEN ... FOR SELECT ...;
|
|
||||||
* ----------
|
|
||||||
*/
|
*/
|
||||||
values = (Datum *) palloc(query->nparams * sizeof(Datum));
|
eval_expr_params(estate, query, &values, &nulls);
|
||||||
nulls = (char *) palloc(query->nparams * sizeof(char));
|
|
||||||
|
|
||||||
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
|
* Open the cursor
|
||||||
* ----------
|
|
||||||
*/
|
*/
|
||||||
portal = SPI_cursor_open(curname, query->plan, values, nulls,
|
portal = SPI_cursor_open(curname, query->plan, values, nulls,
|
||||||
estate->readonly_func);
|
estate->readonly_func);
|
||||||
@ -3217,20 +3069,17 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
|||||||
elog(ERROR, "could not open cursor: %s",
|
elog(ERROR, "could not open cursor: %s",
|
||||||
SPI_result_code_string(SPI_result));
|
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(values);
|
||||||
pfree(nulls);
|
pfree(nulls);
|
||||||
if (curname)
|
if (curname)
|
||||||
pfree(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;
|
return PLPGSQL_RC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4082,7 +3931,6 @@ static int
|
|||||||
exec_run_select(PLpgSQL_execstate *estate,
|
exec_run_select(PLpgSQL_execstate *estate,
|
||||||
PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
|
PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
|
||||||
{
|
{
|
||||||
int i;
|
|
||||||
Datum *values;
|
Datum *values;
|
||||||
char *nulls;
|
char *nulls;
|
||||||
int rc;
|
int rc;
|
||||||
@ -4096,22 +3944,7 @@ exec_run_select(PLpgSQL_execstate *estate,
|
|||||||
/*
|
/*
|
||||||
* Now build up the values and nulls arguments for SPI_execute_plan()
|
* Now build up the values and nulls arguments for SPI_execute_plan()
|
||||||
*/
|
*/
|
||||||
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
|
eval_expr_params(estate, expr, &values, &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] = ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If a portal was requested, put the query into the portal
|
* 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
|
* exec_eval_simple_expr - Evaluate a simple expression returning
|
||||||
* a Datum by directly calling ExecEvalExpr().
|
* 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
|
* 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
|
* exec_eval_using_params --- evaluate params of USING clause
|
||||||
*/
|
*/
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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");
|
return _("FOR with integer loop variable");
|
||||||
case PLPGSQL_STMT_FORS:
|
case PLPGSQL_STMT_FORS:
|
||||||
return _("FOR over SELECT rows");
|
return _("FOR over SELECT rows");
|
||||||
|
case PLPGSQL_STMT_FORC:
|
||||||
|
return _("FOR over cursor");
|
||||||
case PLPGSQL_STMT_EXIT:
|
case PLPGSQL_STMT_EXIT:
|
||||||
return "EXIT";
|
return "EXIT";
|
||||||
case PLPGSQL_STMT_RETURN:
|
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_while(PLpgSQL_stmt_while *stmt);
|
||||||
static void dump_fori(PLpgSQL_stmt_fori *stmt);
|
static void dump_fori(PLpgSQL_stmt_fori *stmt);
|
||||||
static void dump_fors(PLpgSQL_stmt_fors *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_exit(PLpgSQL_stmt_exit *stmt);
|
||||||
static void dump_return(PLpgSQL_stmt_return *stmt);
|
static void dump_return(PLpgSQL_stmt_return *stmt);
|
||||||
static void dump_return_next(PLpgSQL_stmt_return_next *stmt);
|
static void dump_return_next(PLpgSQL_stmt_return_next *stmt);
|
||||||
@ -581,6 +584,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
|
|||||||
case PLPGSQL_STMT_FORS:
|
case PLPGSQL_STMT_FORS:
|
||||||
dump_fors((PLpgSQL_stmt_fors *) stmt);
|
dump_fors((PLpgSQL_stmt_fors *) stmt);
|
||||||
break;
|
break;
|
||||||
|
case PLPGSQL_STMT_FORC:
|
||||||
|
dump_forc((PLpgSQL_stmt_forc *) stmt);
|
||||||
|
break;
|
||||||
case PLPGSQL_STMT_EXIT:
|
case PLPGSQL_STMT_EXIT:
|
||||||
dump_exit((PLpgSQL_stmt_exit *) stmt);
|
dump_exit((PLpgSQL_stmt_exit *) stmt);
|
||||||
break;
|
break;
|
||||||
@ -775,6 +781,29 @@ dump_fors(PLpgSQL_stmt_fors *stmt)
|
|||||||
printf(" ENDFORS\n");
|
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
|
static void
|
||||||
dump_open(PLpgSQL_stmt_open *stmt)
|
dump_open(PLpgSQL_stmt_open *stmt)
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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_WHILE,
|
||||||
PLPGSQL_STMT_FORI,
|
PLPGSQL_STMT_FORI,
|
||||||
PLPGSQL_STMT_FORS,
|
PLPGSQL_STMT_FORS,
|
||||||
|
PLPGSQL_STMT_FORC,
|
||||||
PLPGSQL_STMT_EXIT,
|
PLPGSQL_STMT_EXIT,
|
||||||
PLPGSQL_STMT_RETURN,
|
PLPGSQL_STMT_RETURN,
|
||||||
PLPGSQL_STMT_RETURN_NEXT,
|
PLPGSQL_STMT_RETURN_NEXT,
|
||||||
@ -409,6 +410,21 @@ typedef struct
|
|||||||
} PLpgSQL_stmt_fori;
|
} 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
|
typedef struct
|
||||||
{ /* FOR statement running over SELECT */
|
{ /* FOR statement running over SELECT */
|
||||||
int cmd_type;
|
int cmd_type;
|
||||||
@ -416,10 +432,23 @@ typedef struct
|
|||||||
char *label;
|
char *label;
|
||||||
PLpgSQL_rec *rec;
|
PLpgSQL_rec *rec;
|
||||||
PLpgSQL_row *row;
|
PLpgSQL_row *row;
|
||||||
PLpgSQL_expr *query;
|
|
||||||
List *body; /* List of statements */
|
List *body; /* List of statements */
|
||||||
|
/* end of fields that must match PLpgSQL_stmt_forq */
|
||||||
|
PLpgSQL_expr *query;
|
||||||
} PLpgSQL_stmt_fors;
|
} 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
|
typedef struct
|
||||||
{ /* FOR statement running over EXECUTE */
|
{ /* FOR statement running over EXECUTE */
|
||||||
@ -428,8 +457,9 @@ typedef struct
|
|||||||
char *label;
|
char *label;
|
||||||
PLpgSQL_rec *rec;
|
PLpgSQL_rec *rec;
|
||||||
PLpgSQL_row *row;
|
PLpgSQL_row *row;
|
||||||
PLpgSQL_expr *query;
|
|
||||||
List *body; /* List of statements */
|
List *body; /* List of statements */
|
||||||
|
/* end of fields that must match PLpgSQL_stmt_forq */
|
||||||
|
PLpgSQL_expr *query;
|
||||||
List *params; /* USING expressions */
|
List *params; /* USING expressions */
|
||||||
} PLpgSQL_stmt_dynfors;
|
} 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,
|
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
|
||||||
PLpgSQL_type *dtype,
|
PLpgSQL_type *dtype,
|
||||||
bool add2namespace);
|
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 PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
|
||||||
extern void plpgsql_adddatum(PLpgSQL_datum *new);
|
extern void plpgsql_adddatum(PLpgSQL_datum *new);
|
||||||
extern int plpgsql_add_initdatums(int **varnos);
|
extern int plpgsql_add_initdatums(int **varnos);
|
||||||
|
@ -3151,3 +3151,119 @@ NOTICE: 6
|
|||||||
26
|
26
|
||||||
(1 row)
|
(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;
|
$$ language plpgsql;
|
||||||
|
|
||||||
select exc_using(5, 'foobar');
|
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