diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2008-04-06 23:43:29 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2008-04-06 23:43:29 +0000 |
commit | 347dd6a1cf4ea78e37982ade15496b454c54cf4c (patch) | |
tree | ee44f4a10b60477856fc22217ff6c0eb560cd8bd /src | |
parent | 2604359251d34177a14ef58250d8b4a51d83103b (diff) | |
download | postgresql-347dd6a1cf4ea78e37982ade15496b454c54cf4c.tar.gz postgresql-347dd6a1cf4ea78e37982ade15496b454c54cf4c.zip |
Make plpgsql support FOR over a query specified by a cursor declaration,
for improved compatibility with Oracle.
Pavel Stehule, with some fixes by me.
Diffstat (limited to 'src')
-rw-r--r-- | src/pl/plpgsql/src/gram.y | 233 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_comp.c | 57 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_exec.c | 667 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_funcs.c | 31 | ||||
-rw-r--r-- | src/pl/plpgsql/src/plpgsql.h | 38 | ||||
-rw-r--r-- | src/test/regress/expected/plpgsql.out | 116 | ||||
-rw-r--r-- | src/test/regress/sql/plpgsql.sql | 74 |
7 files changed, 771 insertions, 445 deletions
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index f8b7dd4291c..495b625a450 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -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 diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index c124d071542..ea19a375db7 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -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; } @@ -1516,6 +1491,28 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype, } /* + * 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. */ static PLpgSQL_row * diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index e331b732d35..64ce1d1202d 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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; - - /* - * 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; /* - * 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 */ + rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true); /* - * Now do the loop + * Close the implicit cursor */ - 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); + SPI_cursor_close(portal); - /* - * 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; - } + return rc; +} - /* - * 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. - */ - } +/* ---------- + * 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; - /* - * 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); + /* ---------- + * 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))); + } - return rc; - } - } + /* ---------- + * 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; - SPI_freetuptable(tuptab); + if (curvar->cursor_explicit_argrow < 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("arguments given for cursor without arguments"))); - /* - * Fetch the next 50 tuples - */ - SPI_cursor_fetch(portal, true, 50); - n = SPI_processed; - tuptab = SPI_tuptable; + 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); + /* - * Release last group of tuples + * Now build up the values and nulls arguments for SPI_execute_plan() */ - SPI_freetuptable(tuptab); + eval_expr_params(estate, query, &values, &nulls); /* - * Close the implicit cursor + * Open the cursor */ - SPI_cursor_close(portal); + 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)); /* - * 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. + * If cursor variable was NULL, store the generated portal name in it */ - exec_set_found(estate, found); + if (curname == NULL) + assign_text_var(curvar, portal->name); + + /* + * Execute the loop. We can't prefetch because the cursor is accessible + * to the user, for instance via UPDATE WHERE CURRENT OF within the loop. + */ + rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, false); + + /* ---------- + * Close portal, and restore cursor variable if it was initially NULL. + * ---------- + */ + SPI_cursor_close(portal); + + if (curname == NULL) + { + free_var(curvar); + curvar->value = (Datum) 0; + curvar->isnull = true; + } + + pfree(values); + pfree(nulls); + if (curname) + pfree(curname); return rc; } @@ -2470,7 +2484,6 @@ static int exec_stmt_execsql(PLpgSQL_execstate *estate, PLpgSQL_stmt_execsql *stmt) { - int i; Datum *values; char *nulls; long tcount; @@ -2511,22 +2524,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, /* * Now build up the values and nulls arguments for SPI_execute_plan() */ - values = (Datum *) palloc(expr->nparams * sizeof(Datum)); - nulls = (char *) palloc(expr->nparams * sizeof(char)); - - for (i = 0; i < expr->nparams; i++) - { - PLpgSQL_datum *datum = estate->datums[expr->params[i]]; - Oid paramtypeid; - bool paramisnull; - - exec_eval_datum(estate, datum, expr->plan_argtypes[i], - ¶mtypeid, &values[i], ¶misnull); - if (paramisnull) - nulls[i] = 'n'; - else - nulls[i] = ' '; - } + eval_expr_params(estate, expr, &values, &nulls); /* * If we have INTO, then we only need one row back ... but if we have INTO @@ -2846,22 +2844,8 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt) bool isnull; Oid restype; char *querystr; - PLpgSQL_rec *rec = NULL; - PLpgSQL_row *row = NULL; - SPITupleTable *tuptab; - int n; Portal portal; - bool found = false; - - /* - * Determine if we assign to a record or a row - */ - if (stmt->rec != NULL) - rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); - else if (stmt->row != NULL) - row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); - else - elog(ERROR, "unsupported target"); + int rc; /* * Evaluate the string expression after the EXECUTE keyword. It's result @@ -2910,124 +2894,16 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt) pfree(querystr); /* - * Fetch the initial 10 tuples - */ - 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 + * Execute the loop */ - SPI_freetuptable(tuptab); + rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true); /* * Close the implicit cursor */ SPI_cursor_close(portal); - /* - * Set the FOUND variable to indicate the result of executing the loop - * (namely, whether we looped one or more times). This must be set here so - * that it does not interfere with the value of the FOUND variable inside - * the loop processing itself. - */ - exec_set_found(estate, found); - - return PLPGSQL_RC_OK; + return rc; } @@ -3038,16 +2914,14 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt) static int exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt) { - PLpgSQL_var *curvar = NULL; + PLpgSQL_var *curvar; char *curname = NULL; - PLpgSQL_expr *query = NULL; + PLpgSQL_expr *query; Portal portal; - int i; Datum *values; char *nulls; bool isnull; - /* ---------- * Get the cursor variable and if it has an assigned name, check * that it's not in use currently. @@ -3124,14 +2998,11 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt) pfree(querystr); SPI_freeplan(curplan); - /* ---------- - * Store the eventually assigned cursor name in the cursor variable - * ---------- + /* + * If cursor variable was NULL, store the generated portal name in it */ - free_var(curvar); - curvar->value = CStringGetTextDatum(portal->name); - curvar->isnull = false; - curvar->freeval = true; + if (curname == NULL) + assign_text_var(curvar, portal->name); return PLPGSQL_RC_OK; } @@ -3184,32 +3055,13 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt) exec_prepare_plan(estate, query, curvar->cursor_options); } - /* ---------- - * Here we go if we have a saved plan where we have to put - * values into, either from an explicit cursor or from a - * refcursor opened with OPEN ... FOR SELECT ...; - * ---------- + /* + * Now build up the values and nulls arguments for SPI_execute_plan() */ - values = (Datum *) palloc(query->nparams * sizeof(Datum)); - nulls = (char *) palloc(query->nparams * sizeof(char)); + eval_expr_params(estate, query, &values, &nulls); - for (i = 0; i < query->nparams; i++) - { - PLpgSQL_datum *datum = estate->datums[query->params[i]]; - Oid paramtypeid; - bool paramisnull; - - exec_eval_datum(estate, datum, query->plan_argtypes[i], - ¶mtypeid, &values[i], ¶misnull); - if (paramisnull) - nulls[i] = 'n'; - else - nulls[i] = ' '; - } - - /* ---------- + /* * Open the cursor - * ---------- */ portal = SPI_cursor_open(curname, query->plan, values, nulls, estate->readonly_func); @@ -3217,20 +3069,17 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt) elog(ERROR, "could not open cursor: %s", SPI_result_code_string(SPI_result)); + /* + * If cursor variable was NULL, store the generated portal name in it + */ + if (curname == NULL) + assign_text_var(curvar, portal->name); + pfree(values); pfree(nulls); if (curname) pfree(curname); - /* ---------- - * Store the eventually assigned portal name in the cursor variable - * ---------- - */ - free_var(curvar); - curvar->value = CStringGetTextDatum(portal->name); - curvar->isnull = false; - curvar->freeval = true; - return PLPGSQL_RC_OK; } @@ -4082,7 +3931,6 @@ static int exec_run_select(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, long maxtuples, Portal *portalP) { - int i; Datum *values; char *nulls; int rc; @@ -4096,22 +3944,7 @@ exec_run_select(PLpgSQL_execstate *estate, /* * Now build up the values and nulls arguments for SPI_execute_plan() */ - values = (Datum *) palloc(expr->nparams * sizeof(Datum)); - nulls = (char *) palloc(expr->nparams * sizeof(char)); - - for (i = 0; i < expr->nparams; i++) - { - PLpgSQL_datum *datum = estate->datums[expr->params[i]]; - Oid paramtypeid; - bool paramisnull; - - exec_eval_datum(estate, datum, expr->plan_argtypes[i], - ¶mtypeid, &values[i], ¶misnull); - if (paramisnull) - nulls[i] = 'n'; - else - nulls[i] = ' '; - } + eval_expr_params(estate, expr, &values, &nulls); /* * If a portal was requested, put the query into the portal @@ -4151,6 +3984,154 @@ exec_run_select(PLpgSQL_execstate *estate, } +/* + * exec_for_query --- execute body of FOR loop for each row from a portal + * + * Used by exec_stmt_fors, exec_stmt_forc and exec_stmt_dynfors + */ +static int +exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt, + Portal portal, bool prefetch_ok) +{ + PLpgSQL_rec *rec = NULL; + PLpgSQL_row *row = NULL; + SPITupleTable *tuptab; + bool found = false; + int rc = PLPGSQL_RC_OK; + int n; + + /* + * Determine if we assign to a record or a row + */ + if (stmt->rec != NULL) + rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); + else if (stmt->row != NULL) + row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); + else + elog(ERROR, "unsupported target"); + + /* + * Fetch the initial tuple(s). If prefetching is allowed then we grab + * a few more rows to avoid multiple trips through executor startup + * overhead. + */ + SPI_cursor_fetch(portal, true, prefetch_ok ? 10 : 1); + tuptab = SPI_tuptable; + n = SPI_processed; + + /* + * If the query didn't return any rows, set the target to NULL and + * fall through with found = false. + */ + if (n <= 0) + exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); + else + found = true; /* processed at least one tuple */ + + /* + * Now do the loop + */ + while (n > 0) + { + int i; + + for (i = 0; i < n; i++) + { + /* + * Assign the tuple to the target + */ + exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc); + + /* + * Execute the statements + */ + rc = exec_stmts(estate, stmt->body); + + if (rc != PLPGSQL_RC_OK) + { + if (rc == PLPGSQL_RC_EXIT) + { + if (estate->exitlabel == NULL) + { + /* unlabelled exit, so exit the current loop */ + rc = PLPGSQL_RC_OK; + } + else if (stmt->label != NULL && + strcmp(stmt->label, estate->exitlabel) == 0) + { + /* label matches this loop, so exit loop */ + estate->exitlabel = NULL; + rc = PLPGSQL_RC_OK; + } + + /* + * otherwise, we processed a labelled exit that does not + * match the current statement's label, if any; return + * RC_EXIT so that the EXIT continues to recurse upward. + */ + } + else if (rc == PLPGSQL_RC_CONTINUE) + { + if (estate->exitlabel == NULL) + { + /* unlabelled continue, so re-run the current loop */ + rc = PLPGSQL_RC_OK; + continue; + } + else if (stmt->label != NULL && + strcmp(stmt->label, estate->exitlabel) == 0) + { + /* label matches this loop, so re-run loop */ + estate->exitlabel = NULL; + rc = PLPGSQL_RC_OK; + continue; + } + + /* + * otherwise, we process a labelled continue that does not + * match the current statement's label, if any; return + * RC_CONTINUE so that the CONTINUE will propagate up the + * stack. + */ + } + + /* + * We're aborting the loop. Need a goto to get out of two + * levels of loop... + */ + goto loop_exit; + } + } + + SPI_freetuptable(tuptab); + + /* + * Fetch more tuples. If prefetching is allowed, grab 50 at a time. + */ + SPI_cursor_fetch(portal, true, prefetch_ok ? 50 : 1); + tuptab = SPI_tuptable; + n = SPI_processed; + } + +loop_exit: + + /* + * Release last group of tuples (if any) + */ + SPI_freetuptable(tuptab); + + /* + * Set the FOUND variable to indicate the result of executing the loop + * (namely, whether we looped one or more times). This must be set last so + * that it does not interfere with the value of the FOUND variable inside + * the loop processing itself. + */ + exec_set_found(estate, found); + + return rc; +} + + /* ---------- * exec_eval_simple_expr - Evaluate a simple expression returning * a Datum by directly calling ExecEvalExpr(). @@ -4317,6 +4298,36 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, } +/* + * Build up the values and nulls arguments for SPI_execute_plan() + */ +static void +eval_expr_params(PLpgSQL_execstate *estate, + PLpgSQL_expr *expr, Datum **p_values, char **p_nulls) +{ + Datum *values; + char *nulls; + int i; + + *p_values = values = (Datum *) palloc(expr->nparams * sizeof(Datum)); + *p_nulls = nulls = (char *) palloc(expr->nparams * sizeof(char)); + + for (i = 0; i < expr->nparams; i++) + { + PLpgSQL_datum *datum = estate->datums[expr->params[i]]; + Oid paramtypeid; + bool paramisnull; + + exec_eval_datum(estate, datum, expr->plan_argtypes[i], + ¶mtypeid, &values[i], ¶misnull); + if (paramisnull) + nulls[i] = 'n'; + else + nulls[i] = ' '; + } +} + + /* ---------- * exec_move_row Move one tuple's values into a record or row * ---------- @@ -5110,6 +5121,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 */ static PreparedParamsData * diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index be57154e402..1a68c9339f2 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -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; @@ -776,6 +782,29 @@ dump_fors(PLpgSQL_stmt_fors *stmt) } 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) { dump_ind(); diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 66588b343f8..a50d5721f43 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -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); diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index bcf974483ea..018c8c2b50f 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -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 diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 2a93ffc611e..066ccbeba63 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -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; |