1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-20 05:03:10 +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:
Tom Lane
2008-04-06 23:43:29 +00:00
parent 2604359251
commit 347dd6a1cf
8 changed files with 947 additions and 568 deletions

View File

@ -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

View File

@ -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.
*/

View File

@ -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],
&paramtypeid, &values[i], &paramisnull);
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],
&paramtypeid, &values[i], &paramisnull);
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],
&paramtypeid, &values[i], &paramisnull);
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],
&paramtypeid, &values[i], &paramisnull);
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
*/

View File

@ -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)
{

View File

@ -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);

View File

@ -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

View File

@ -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;