Index: doc/src/sgml/plsql.sgml
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/doc/src/sgml/plsql.sgml,v
retrieving revision 2.25
diff -u -r2.25 plsql.sgml
--- doc/src/sgml/plsql.sgml 2001/03/23 22:07:50 2.25
+++ doc/src/sgml/plsql.sgml 2001/03/25 01:14:43
@@ -424,6 +424,90 @@
+
+ Cursor Variables
+
+
+ You can declare a cursor variable to hold the results of a
+ query. The syntax is
+
+CURSOR name (parameters) IS SELECT expressions;
+
+
+
+
+ Cursors may be used with the FOR statement, or with the OPEN,
+ FETCH, and CLOSE statements. Cursors in PL/pgSQL should not be
+ confused with cursors in SQL; they are very similar, but not
+ identical.
+
+
+
+ A cursor may optionally have comma separated parameters. Each
+ parameters has a name and a type, and an optional default value.
+
+name IN type := value
+
+ Parameter values are specified when the cursor is used in a FOR
+ or OPEN statement.
+
+
+
+ Cursors automatically define four additional variables, which
+ have names which start with the name of the cursor:
+
+
+
+ name%ISOPEN
+
+
+
+ True if the cursor is open, false otherwise.
+
+
+
+
+
+ name%FOUND
+
+
+
+ True if the last FETCH from a cursor retrieved data.
+
+
+
+
+
+ name%NOTFOUND
+
+
+
+ True if the last FETCH from a cursor did not retrieve any
+ data, because all the data has been exhausted.
+
+
+
+
+
+ name%ROWCOUNT
+
+
+
+ The number of rows fetched from a cursor.
+
+
+
+
+
+
+
+ If the cursor uses the FOR UPDATE clause, then, after doing a
+ FETCH from the cursor, you may use CURRENT OF
+ cursor in a WHERE clause of an
+ UPDATE. This will update the row which was just fetched.
+
+
+
Attributes
@@ -1295,6 +1379,56 @@
flexibility of a dynamic query, just as with a plain EXECUTE
statement.
+
+
+ You can also use a FOR statement to iterate over all the records
+ specified by a cursor:
+
+<<label>>
+FOR record | row IN cursor LOOP
+ statements
+END LOOP;
+
+ This is like the previous examples, but the specified cursor is
+ used for the source SELECT statement.
+
+
+
+
+ Using Cursors
+
+
+ For a more flexible way to iterate through records, you can use
+ cursors. The OPEN statement is used to open the query associated
+ with a cursor, the FETCH statement is used to fetch the next
+ record associated with a cursor, and the CLOSE statement is used
+ to close a cursor. For an example of how to use these
+ statements, see .
+
+
+
+ Before you can FETCH any values from a cursor, you must OPEN it:
+
+OPEN cursor;
+
+
+
+
+ After a cursor is open, you can fetch the next record into
+ specified variables.
+
+FETCH cursor INTO record | row | variables;
+
+ This will fetch the next value of a cursor into a record, or a
+ row, or a comma separated list of variables.
+
+
+
+ After you are through with a cursor, you should close it.
+
+CLOSE cursor;
+
+
@@ -1648,6 +1782,41 @@
' LANGUAGE 'plpgsql';
+
+
+ Cursor example
+
+ Here is a simple example of stepping through a cursor and using
+ it to update values.
+
+
+CREATE FUNCTION cursortest() RETURNS int4 AS '
+ DECLARE
+ CURSOR mycursor(lname VARCHAR(25)) IS SELECT * FROM users
+ WHERE lastname = lname FOR UPDATE;
+ myrec mycursor%ROWTYPE;
+ c int4;
+ BEGIN
+ OPEN mycursor(''Taylor'');
+ c := 0;
+ LOOP
+ FETCH mycursor INTO myrec;
+ IF mycursor%NOTFOUND THEN
+ EXIT;
+ END IF;
+ IF myrec.doupdate THEN
+ UPDATE users SET updatetime = ''now''
+ WHERE CURRENT OF mycursor;
+ c := c + 1;
+ END IF;
+ END LOOP;
+ CLOSE mycursor;
+ RETURN c;
+ END;
+' LANGUAGE 'plpgsql';
+
+
+
@@ -1727,13 +1896,6 @@
Assignments, loops and conditionals are similar.
-
-
-
-
-
- No need for cursors in PostgreSQL, just put the query in the FOR
- statement (see example below)
Index: src/pl/plpgsql/src/gram.y
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v
retrieving revision 1.16
diff -u -r1.16 gram.y
--- src/pl/plpgsql/src/gram.y 2001/02/19 19:49:53 1.16
+++ src/pl/plpgsql/src/gram.y 2001/03/25 01:14:50
@@ -38,6 +38,7 @@
#include
#include
+#include
#include "plpgsql.h"
#ifdef YYBISON
#include "pl_scan.c" /* GNU bison wants it here */
@@ -45,9 +46,13 @@
-static PLpgSQL_expr *read_sqlstmt(int until, char *s, char *sqlstart);
+static PLpgSQL_expr *read_sqlstmt(int until, int until2, char *s,
+ char *sqlstart, int *end);
static PLpgSQL_stmt *make_select_stmt(void);
static PLpgSQL_expr *make_tupret_expr(PLpgSQL_row *row);
+static int make_cursor_var(char *cursor, char *attr,
+ int lineno, char *type,
+ PLpgSQL_expr *defval);
%}
@@ -82,12 +87,19 @@
int n_initvars;
int *initvarnos;
} declhdr;
+ struct
+ {
+ int nalloc;
+ int nused;
+ PLpgSQL_expr **exprs;
+ } explist;
PLpgSQL_type *dtype;
PLpgSQL_var *var;
PLpgSQL_row *row;
PLpgSQL_rec *rec;
PLpgSQL_recfield *recfield;
PLpgSQL_trigarg *trigarg;
+ PLpgSQL_cursor *cursor;
PLpgSQL_expr *expr;
PLpgSQL_stmt *stmt;
PLpgSQL_stmts *stmts;
@@ -99,13 +111,16 @@
%type decl_varname
%type decl_renname
%type decl_const, decl_notnull, decl_atttypmod, decl_atttypmodval
-%type decl_defval
-%type decl_datatype, decl_dtypename
+%type decl_defval, decl_cursor_expr, decl_cursor_default
+%type decl_datatype, decl_dtypename, decl_cursor_return
%type decl_rowtype
%type decl_aliasitem
%type decl_stmts, decl_stmt
+%type decl_cursor_param_decl, decl_cursor_params
+%type decl_cursor_param
%type expr_until_semi, expr_until_then, expr_until_loop
+%type expr_until_comma_or_paren
%type opt_exitcond
%type assign_var
@@ -113,6 +128,9 @@
%type fori_varname
%type fori_lower
%type fors_target
+%type cursor
+%type cursor_params, cursor_param_vals
+%type cursor_param
%type opt_lblname, opt_label
%type opt_exitlabel
@@ -122,13 +140,19 @@
%type proc_stmt, pl_block
%type stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit
%type stmt_return, stmt_raise, stmt_execsql, stmt_fori
-%type stmt_fors, stmt_select, stmt_perform
+%type stmt_fors, stmt_forc, stmt_select, stmt_perform
%type stmt_dynexecute, stmt_dynfors, stmt_getdiag
+%type stmt_open, stmt_fetch, stmt_close
%type raise_params
%type raise_level, raise_param
%type raise_msg
+%type fetch_vars
+%type fetch_var
+%type fetch_record
+%type fetch_row
+
%type getdiag_list
%type getdiag_item, getdiag_target
@@ -140,7 +164,9 @@
%token K_ALIAS
%token K_ASSIGN
%token K_BEGIN
+%token K_CLOSE
%token K_CONSTANT
+%token K_CURSOR
%token K_DEBUG
%token K_DECLARE
%token K_DEFAULT
@@ -151,6 +177,7 @@
%token K_EXCEPTION
%token K_EXECUTE
%token K_EXIT
+%token K_FETCH
%token K_FOR
%token K_FROM
%token K_GET
@@ -161,6 +188,7 @@
%token K_NOT
%token K_NOTICE
%token K_NULL
+%token K_OPEN
%token K_PERFORM
%token K_ROW_COUNT
%token K_RAISE
@@ -182,6 +210,7 @@
%token T_FUNCTION
%token T_TRIGGER
%token T_CHAR
+%token T_CURSOR
%token T_BPCHAR
%token T_VARCHAR
%token T_LABEL
@@ -346,6 +375,72 @@
{
plpgsql_ns_rename($2, $4);
}
+ | decl_cursor_start decl_varname decl_cursor_param_decl decl_cursor_return decl_cursor_expr
+ {
+ PLpgSQL_cursor *new;
+ PLpgSQL_expr *defval;
+
+ new = malloc(sizeof(PLpgSQL_cursor));
+ new->dtype = PLPGSQL_DTYPE_CURSOR;
+ new->refname = $2.name;
+ new->lineno = $2.lineno;
+
+ new->select = $5;
+ new->n_params = $3.nused;
+ if ($3.nused == 0)
+ new->params = NULL;
+ else
+ {
+ new->params = malloc($3.nused * sizeof(int));
+ memcpy(new->params, $3.nums,
+ $3.nused * sizeof(int));
+ pfree($3.nums);
+ }
+
+ new->tuptable = NULL;
+ new->count = 0;
+
+ plpgsql_ns_pop();
+
+ defval = malloc(sizeof(PLpgSQL_expr) - 1);
+ defval->dtype = PLPGSQL_DTYPE_EXPR;
+ defval->query = strdup("SELECT FALSE");
+ defval->plan = NULL;
+ defval->nparams = 0;
+
+ new->found_varno = make_cursor_var($2.name,
+ "found",
+ $2.lineno,
+ "bool",
+ NULL);
+ new->isopen_varno = make_cursor_var($2.name,
+ "isopen",
+ $2.lineno,
+ "bool",
+ defval);
+ new->notfound_varno = make_cursor_var($2.name,
+ "notfound",
+ $2.lineno,
+ "bool",
+ NULL);
+ new->rowcount_varno = make_cursor_var($2.name,
+ "rowcount",
+ $2.lineno,
+ "int4",
+ NULL);
+ new->oid_varno = make_cursor_var($2.name,
+ "oid",
+ $2.lineno,
+ "int4",
+ NULL);
+ new->saw_current_of = false;
+ new->oid_added = false;
+
+ plpgsql_adddatum((PLpgSQL_datum *) new);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_CURSOR,
+ new->cursorno,
+ $2.name);
+ }
;
decl_aliasitem : T_WORD
@@ -509,6 +604,121 @@
decl_defkey : K_ASSIGN
| K_DEFAULT
+decl_cursor_start : K_CURSOR
+ {
+ plpgsql_ns_push(NULL);
+ /* Note that decl_start called ns_setlocal(true). */
+ }
+ ;
+
+decl_cursor_expr : decl_cursor_is K_SELECT expr_until_semi
+ {
+ $$ = $3;
+ }
+ ;
+
+decl_cursor_param_decl : /* empty */
+ {
+ $$.nalloc = 0;
+ $$.nused = 0;
+ $$.nums = NULL;
+ }
+ | '(' decl_cursor_params ')'
+ {
+ $$ = $2;
+ }
+ ;
+
+decl_cursor_params : decl_cursor_param
+ {
+ $$.nalloc = 1;
+ $$.nused = 1;
+ $$.nums = palloc(sizeof(int) * $$.nalloc);
+ $$.nums[0] = $1;
+ }
+ | decl_cursor_params ',' decl_cursor_param
+ {
+ if ($1.nused >= $1.nalloc)
+ {
+ $1.nalloc *= 2;
+ $1.nums = repalloc($1.nums,
+ sizeof(int) * $1.nalloc);
+ }
+ $1.nums[$1.nused] = $3;
+ ++$1.nused;
+
+ $$ = $1;
+ }
+ ;
+
+decl_cursor_param : decl_varname decl_cursor_optin decl_datatype decl_cursor_default
+ {
+ PLpgSQL_var *new;
+
+ new = malloc(sizeof(PLpgSQL_var));
+
+ new->dtype = PLPGSQL_DTYPE_VAR;
+ new->refname = $1.name;
+ new->lineno = $1.lineno;
+
+ new->datatype = $3;
+ new->isconst = false;
+ new->notnull = false;
+ new->default_val = $4;
+
+ plpgsql_adddatum((PLpgSQL_datum *) new);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, new->varno,
+ $1.name);
+
+ $$ = new->varno;
+ }
+ ;
+
+decl_cursor_optin : /* empty */
+ | K_IN
+ ;
+
+decl_cursor_default : /* empty */
+ {
+ $$ = NULL;
+ }
+ | decl_defkey expr_until_comma_or_paren
+ {
+ $$ = $2;
+ }
+ ;
+
+decl_cursor_return : /* empty */
+ {
+ $$ = NULL;
+ }
+ | K_RETURN decl_cursor_returntype
+ {
+ yyerror("cursor return type not supported");
+ $$ = NULL;
+ }
+ ;
+
+decl_cursor_returntype : T_ROW
+ | T_DTYPE
+ ;
+
+decl_cursor_is : /* empty */
+ | T_WORD
+ {
+ char *is;
+
+ /* `is' is just a noise word in the syntax.
+ * Avoid making it a keyword by checking for
+ * it here.
+ */
+ is = plpgsql_tolower(yytext);
+ if (strcmp(is, "is") != 0)
+ yyerror("expected IS");
+ pfree(is);
+ }
+ ;
+
proc_sect :
{
PLpgSQL_stmts *new;
@@ -561,6 +771,8 @@
{ $$ = $1; }
| stmt_fors
{ $$ = $1; }
+ | stmt_forc
+ { $$ = $1; }
| stmt_select
{ $$ = $1; }
| stmt_exit
@@ -579,6 +791,12 @@
{ $$ = $1; }
| stmt_getdiag
{ $$ = $1; }
+ | stmt_open
+ { $$ = $1; }
+ | stmt_fetch
+ { $$ = $1; }
+ | stmt_close
+ { $$ = $1; }
;
stmt_perform : K_PERFORM lno expr_until_semi
@@ -964,6 +1182,94 @@
}
;
+stmt_forc : opt_label K_FOR lno fors_target K_IN cursor cursor_params K_LOOP loop_body
+ {
+ PLpgSQL_stmt_forc *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_forc));
+ memset(new, 0, sizeof(PLpgSQL_stmt_forc));
+
+ new->cmd_type = PLPGSQL_STMT_FORC;
+ new->lineno = $3;
+ new->label = $1;
+ switch ($4->dtype) {
+ case PLPGSQL_DTYPE_REC:
+ new->rec = $4;
+ break;
+ case PLPGSQL_DTYPE_ROW:
+ new->row = (PLpgSQL_row *)$4;
+ break;
+ default:
+ plpgsql_comperrinfo();
+ elog(ERROR, "unknown dtype %d in stmt_forc",
+ $4->dtype);
+ }
+ new->cursor = $6;
+ new->nparams = $7.nused;
+ if ($7.nused == 0)
+ new->params = NULL;
+ else
+ {
+ new->params = malloc($7.nused * sizeof(int));
+ memcpy(new->params, $7.exprs,
+ $7.nused * sizeof(int));
+ pfree($7.exprs);
+ }
+ new->body = $9;
+
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+cursor : T_CURSOR
+ {
+ $$ = yylval.cursor;
+ }
+ ;
+
+cursor_params : /* empty */
+ {
+ $$.nused = 0;
+ $$.nalloc = 0;
+ $$.exprs = NULL;
+ }
+ | '(' cursor_param_vals ')'
+ {
+ $$ = $2;
+ }
+ ;
+
+cursor_param_vals : cursor_param
+ {
+ $$.nalloc = 1;
+ $$.nused = 1;
+ $$.exprs = palloc($$.nalloc * sizeof(PLpgSQL_expr *));
+ $$.exprs[0] = $1;
+ }
+ | cursor_param_vals ',' cursor_param
+ {
+ if ($1.nused >= $1.nalloc)
+ {
+ $1.nalloc *= 2;
+ $1.exprs = repalloc($1.exprs,
+ ($1.nalloc
+ * sizeof(PLpgSQL_expr *)));
+ }
+ $1.exprs[$1.nused] = $3;
+ ++$1.nused;
+
+ $$ = $1;
+ }
+ ;
+
+cursor_param : expr_until_comma_or_paren
+ {
+ $$ = $1;
+ }
+ ;
+
stmt_select : K_SELECT lno
{
$$ = make_select_stmt();
@@ -1134,7 +1440,7 @@
new = malloc(sizeof(PLpgSQL_stmt_execsql));
new->cmd_type = PLPGSQL_STMT_EXECSQL;
new->lineno = $2;
- new->sqlstmt = read_sqlstmt(';', ";", $1);
+ new->sqlstmt = read_sqlstmt(';', ';', ";", $1, NULL);
$$ = (PLpgSQL_stmt *)new;
}
@@ -1159,6 +1465,132 @@
{ $$ = strdup(yytext); }
;
+stmt_open : K_OPEN lno cursor cursor_params ';'
+ {
+ PLpgSQL_stmt_open *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_open));
+ new->cmd_type = PLPGSQL_STMT_OPEN;
+ new->lineno = $2;
+ new->cursor = $3;
+ new->nparams = $4.nused;
+ if (new->nparams == 0)
+ new->params = NULL;
+ else
+ {
+ new->params = malloc($4.nused * sizeof(int));
+ memcpy(new->params, $4.exprs,
+ $4.nused * sizeof(int));
+ pfree($4.exprs);
+ }
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_fetch : K_FETCH lno cursor K_INTO fetch_vars ';'
+ {
+ PLpgSQL_stmt_fetch *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_fetch));
+ new->cmd_type = PLPGSQL_STMT_FETCH;
+ new->lineno = $2;
+ new->cursor = $3;
+ new->nvars = $5.nused;
+ new->varnos = malloc($5.nused * sizeof(int));
+ memcpy(new->varnos, $5.nums,
+ $5.nused * sizeof(int));
+ pfree($5.nums);
+ new->rec = NULL;
+ new->row = NULL;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ | K_FETCH lno cursor K_INTO fetch_record ';'
+ {
+ PLpgSQL_stmt_fetch *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_fetch));
+ new->cmd_type = PLPGSQL_STMT_FETCH;
+ new->lineno = $2;
+ new->cursor = $3;
+ new->nvars = 0;
+ new->varnos = NULL;
+ new->rec = $5;
+ new->row = NULL;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ | K_FETCH lno cursor K_INTO fetch_row ';'
+ {
+ PLpgSQL_stmt_fetch *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_fetch));
+ new->cmd_type = PLPGSQL_STMT_FETCH;
+ new->lineno = $2;
+ new->cursor = $3;
+ new->nvars = 0;
+ new->varnos = NULL;
+ new->rec = NULL;
+ new->row = $5;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+fetch_record : T_RECORD
+ {
+ $$ = yylval.rec;
+ }
+ ;
+
+fetch_row : T_ROW
+ {
+ $$ = yylval.row;
+ }
+ ;
+
+fetch_vars : fetch_var
+ {
+ $$.nalloc = 1;
+ $$.nused = 1;
+ $$.nums = palloc(sizeof(int) * $$.nalloc);
+ $$.nums[0] = $1;
+ }
+ | fetch_vars ',' fetch_var
+ {
+ if ($1.nused >= $1.nalloc)
+ {
+ $1.nalloc *= 2;
+ $1.nums = repalloc($1.nums,
+ sizeof(int) * $1.nalloc);
+ }
+ $1.nums[$1.nused] = $3;
+ ++$1.nused;
+
+ $$ = $1;
+ }
+ ;
+
+fetch_var : T_VARIABLE
+ {
+ $$ = yylval.var->varno;
+ }
+ ;
+
+stmt_close : K_CLOSE lno cursor ';'
+ {
+ PLpgSQL_stmt_close *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_close));
+ new->cmd_type = PLPGSQL_STMT_CLOSE;
+ new->lineno = $2;
+ new->cursor = $3;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
expr_until_semi :
{ $$ = plpgsql_read_expression(';', ";"); }
;
@@ -1171,6 +1603,15 @@
{ $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
;
+expr_until_comma_or_paren :
+ {
+ int end;
+
+ $$ = read_sqlstmt(',', ')', ", or )", "SELECT ", &end);
+ unput(end);
+ }
+ ;
+
opt_label :
{
plpgsql_ns_push(NULL);
@@ -1216,12 +1657,12 @@
PLpgSQL_expr *
plpgsql_read_expression (int until, char *s)
{
- return read_sqlstmt(until, s, "SELECT ");
+ return read_sqlstmt(until, until, s, "SELECT ", NULL);
}
static PLpgSQL_expr *
-read_sqlstmt (int until, char *s, char *sqlstart)
+read_sqlstmt (int until, int until2, char *s, char *sqlstart, int *end)
{
int tok;
int lno;
@@ -1230,12 +1671,14 @@
int params[1024];
char buf[32];
PLpgSQL_expr *expr;
+ int current_of_state = 0;
+ char c;
lno = yylineno;
plpgsql_dstring_init(&ds);
plpgsql_dstring_append(&ds, sqlstart);
- while((tok = yylex()) != until)
+ while((tok = yylex()) != until && tok != until2)
{
if (tok == ';') break;
if (plpgsql_SpaceScanned)
@@ -1246,20 +1689,58 @@
params[nparams] = yylval.var->varno;
sprintf(buf, " $%d ", ++nparams);
plpgsql_dstring_append(&ds, buf);
+ current_of_state = 0;
break;
case T_RECFIELD:
params[nparams] = yylval.recfield->rfno;
sprintf(buf, " $%d ", ++nparams);
plpgsql_dstring_append(&ds, buf);
+ current_of_state = 0;
break;
case T_TGARGV:
params[nparams] = yylval.trigarg->dno;
sprintf(buf, " $%d ", ++nparams);
plpgsql_dstring_append(&ds, buf);
+ current_of_state = 0;
break;
+ case T_CURSOR:
+ /* Look specially for ``CURRENT OF cursor'', and
+ * convert it into a reference to the cursor OID
+ * variable. This is a real hack, but I don't think
+ * there is any other way to do it short of parsing
+ * the whole statement here.
+ */
+ if (current_of_state == 2)
+ {
+ char *str;
+ char *cp;
+
+ /* Whitespace is stripped by the lexer, so we can
+ * use single spaces here.
+ */
+ str = "current of ";
+ cp = plpgsql_tolower(plpgsql_dstring_get(&ds));
+ if (strcmp(cp + ds.used - strlen(str), str) != 0)
+ elog(ERROR, "read_sqlstmt: internal error");
+ pfree(cp);
+
+ ds.used -= strlen(str);
+ plpgsql_dstring_append(&ds, "oid = ");
+ params[nparams] = yylval.cursor->oid_varno;
+ sprintf(buf, " $%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+
+ yylval.cursor->saw_current_of = true;
+
+ break;
+ }
+ plpgsql_dstring_append(&ds, yytext);
+ current_of_state = 0;
+ break;
+
default:
if (tok == 0)
{
@@ -1268,10 +1749,34 @@
elog(ERROR, "missing %s at end of SQL statement", s);
}
plpgsql_dstring_append(&ds, yytext);
+
+ c = yytext[0];
+ if (isupper(c))
+ c = tolower(c);
+ if ((current_of_state == 0
+ && c == 'c')
+ || (current_of_state == 1
+ && c == 'o'))
+ {
+ if (current_of_state == 0
+ && strcasecmp(yytext, "current") == 0)
+ current_of_state = 1;
+ else if (current_of_state == 1
+ && strcasecmp(yytext, "of") == 0)
+ current_of_state = 2;
+ else
+ current_of_state = 0;
+ }
+ else
+ current_of_state = 0;
+
break;
}
}
+ if (end != NULL)
+ *end = tok;
+
expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - 1);
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = strdup(plpgsql_dstring_get(&ds));
@@ -1597,4 +2102,38 @@
plpgsql_dstring_free(&ds);
return expr;
+}
+
+
+/* Cursor attributes are handled by defining variables with magic
+ * names.
+ */
+static int
+make_cursor_var(char *cursor, char *attr, int lineno, char *type,
+ PLpgSQL_expr *defval)
+{
+ char *s;
+ PLpgSQL_var *new;
+
+ s = malloc(strlen(cursor) + strlen(attr) + 2);
+ sprintf(s, "%s%%%s", cursor, attr);
+
+ new = malloc(sizeof(PLpgSQL_var));
+
+ new->dtype = PLPGSQL_DTYPE_VAR;
+ new->refname = s;
+ new->lineno = lineno;
+
+ if (plpgsql_parse_word(type) != T_DTYPE)
+ elog(ERROR, "internal error: '%s' is not a type", type);
+
+ new->datatype = yylval.dtype;
+ new->isconst = false;
+ new->notnull = false;
+ new->default_val = defval;
+
+ plpgsql_adddatum((PLpgSQL_datum *)new);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, new->varno, s);
+
+ return new->varno;
}
Index: src/pl/plpgsql/src/pl_comp.c
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v
retrieving revision 1.28
diff -u -r1.28 pl_comp.c
--- src/pl/plpgsql/src/pl_comp.c 2001/03/22 06:16:21 1.28
+++ src/pl/plpgsql/src/pl_comp.c 2001/03/25 01:14:50
@@ -576,6 +576,10 @@
plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[nse->itemno]);
return T_ROW;
+ case PLPGSQL_NSTYPE_CURSOR:
+ plpgsql_yylval.cursor = (PLpgSQL_cursor *) (plpgsql_Datums[nse->itemno]);
+ return T_CURSOR;
+
default:
return T_ERROR;
}
@@ -1083,12 +1087,13 @@
/* ----------
* plpgsql_parse_wordrowtype Scanner found word%ROWTYPE.
- * So word must be a table name.
+ * So word must be a table name or a cursor.
* ----------
*/
int
plpgsql_parse_wordrowtype(char *string)
{
+ PLpgSQL_nsitem *nse;
HeapTuple classtup;
Form_pg_class classStruct;
HeapTuple typetup;
@@ -1108,6 +1113,19 @@
cp = strchr(word1, '%');
*cp = '\0';
+ nse = plpgsql_ns_lookup(word1, NULL);
+ if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_CURSOR)
+ {
+ /* We only accept cursor%ROWTYPE in a declaration. And for
+ * simplicity we don't care about the type of the record--we
+ * just look up the fields when we have their names. So we
+ * treat this as though it were the record keyword. This
+ * works correctly, though it doesn't permit much error
+ * checking.
+ */
+ return K_RECORD;
+ }
+
classtup = SearchSysCache(RELNAME,
PointerGetDatum(word1),
0, 0, 0);
@@ -1235,10 +1253,27 @@
/* ----------
- * plpgsql_adddatum Add a variable, record or row
- * to the compilers datum list.
+ * plpgsql_parse_attribute Parse a cursor attribute.
* ----------
*/
+int
+plpgsql_parse_attribute(char *string)
+{
+ /* Cursor attributes are entered into the namespace tables with an
+ * embedded %, so they look exactly like the strings the user
+ * uses. This makes this function easy.
+ */
+
+ if (plpgsql_parse_word(string) != T_VARIABLE)
+ return T_ERROR;
+ return T_VARIABLE;
+}
+
+
+/* ----------
+ * plpgsql_adddatum Add a variable, record or row
+ * to the compilers datum list.
+ * ---------- */
void
plpgsql_adddatum(PLpgSQL_datum * new)
{
Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.40
diff -u -r1.40 pl_exec.c
--- src/pl/plpgsql/src/pl_exec.c 2001/03/22 06:16:21 1.40
+++ src/pl/plpgsql/src/pl_exec.c 2001/03/25 01:14:51
@@ -72,6 +72,7 @@
************************************************************/
static PLpgSQL_var *copy_var(PLpgSQL_var * var);
static PLpgSQL_rec *copy_rec(PLpgSQL_rec * rec);
+static PLpgSQL_cursor *copy_cursor(PLpgSQL_cursor * cursor);
static int exec_stmt_block(PLpgSQL_execstate * estate,
PLpgSQL_stmt_block * block);
@@ -91,8 +92,13 @@
PLpgSQL_stmt_while * stmt);
static int exec_stmt_fori(PLpgSQL_execstate * estate,
PLpgSQL_stmt_fori * stmt);
+static int exec_stmt_forsc(PLpgSQL_execstate * estate, char * label,
+ PLpgSQL_rec * rec, PLpgSQL_row * row, PLpgSQL_expr * query,
+ PLpgSQL_stmts * body, int skip);
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_select(PLpgSQL_execstate * estate,
PLpgSQL_stmt_select * stmt);
static int exec_stmt_exit(PLpgSQL_execstate * estate,
@@ -107,6 +113,12 @@
PLpgSQL_stmt_dynexecute * stmt);
static int exec_stmt_dynfors(PLpgSQL_execstate * estate,
PLpgSQL_stmt_dynfors * stmt);
+static int exec_stmt_open(PLpgSQL_execstate * estate,
+ PLpgSQL_stmt_open * stmt);
+static int exec_stmt_fetch(PLpgSQL_execstate * estate,
+ PLpgSQL_stmt_fetch * stmt);
+static int exec_stmt_close(PLpgSQL_execstate * estate,
+ PLpgSQL_stmt_close * stmt);
static void exec_prepare_plan(PLpgSQL_execstate * estate,
PLpgSQL_expr * expr);
@@ -132,7 +144,7 @@
static void exec_move_row(PLpgSQL_execstate * estate,
PLpgSQL_rec * rec,
PLpgSQL_row * row,
- HeapTuple tup, TupleDesc tupdesc);
+ HeapTuple tup, TupleDesc tupdesc, int skip);
static Datum exec_cast_value(Datum value, Oid valtype,
Oid reqtype,
FmgrInfo *reqinput,
@@ -140,6 +152,10 @@
int32 reqtypmod,
bool *isnull);
static void exec_set_found(PLpgSQL_execstate * estate, bool state);
+static void exec_set_cursor_params(PLpgSQL_execstate * estate,
+ PLpgSQL_cursor * cursor, int nparams,
+ PLpgSQL_expr ** params);
+static void exec_cursor_add_oid(PLpgSQL_cursor * cursor);
/* ----------
@@ -211,6 +227,9 @@
case PLPGSQL_STMT_FORS:
stmttype = "for over select rows";
break;
+ case PLPGSQL_STMT_FORC:
+ stmttype = "for over cursor";
+ break;
case PLPGSQL_STMT_SELECT:
stmttype = "select into variables";
break;
@@ -232,6 +251,15 @@
case PLPGSQL_STMT_DYNFORS:
stmttype = "for over execute statement";
break;
+ case PLPGSQL_STMT_OPEN:
+ stmttype = "open";
+ break;
+ case PLPGSQL_STMT_FETCH:
+ stmttype = "fetch";
+ break;
+ case PLPGSQL_STMT_CLOSE:
+ stmttype = "close";
+ break;
default:
stmttype = "unknown";
break;
@@ -287,6 +315,10 @@
copy_rec((PLpgSQL_rec *) (func->datums[i]));
break;
+ case PLPGSQL_DTYPE_CURSOR:
+ estate.datums[i] = (PLpgSQL_datum *)
+ copy_cursor((PLpgSQL_cursor *) (func->datums[i]));
+
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_RECFIELD:
estate.datums[i] = func->datums[i];
@@ -328,7 +360,7 @@
Assert(slot != NULL && !fcinfo->argnull[i]);
tup = slot->val;
tupdesc = slot->ttc_tupleDescriptor;
- exec_move_row(&estate, NULL, row, tup, tupdesc);
+ exec_move_row(&estate, NULL, row, tup, tupdesc, 0);
}
break;
@@ -357,6 +389,16 @@
}
break;
+ case PLPGSQL_DTYPE_CURSOR:
+ {
+ PLpgSQL_cursor *cur = (PLpgSQL_cursor *) estate.datums[i];
+
+ cur->tuptable = NULL;
+ cur->count = 0;
+ cur->index = -1;
+ }
+ break;
+
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_REC:
case PLPGSQL_DTYPE_RECFIELD:
@@ -511,6 +553,9 @@
case PLPGSQL_STMT_FORS:
stmttype = "for over select rows";
break;
+ case PLPGSQL_STMT_FORC:
+ stmttype = "for over cursor";
+ break;
case PLPGSQL_STMT_SELECT:
stmttype = "select into variables";
break;
@@ -532,6 +577,15 @@
case PLPGSQL_STMT_DYNFORS:
stmttype = "for over execute statement";
break;
+ case PLPGSQL_STMT_OPEN:
+ stmttype = "open";
+ break;
+ case PLPGSQL_STMT_FETCH:
+ stmttype = "fetch";
+ break;
+ case PLPGSQL_STMT_CLOSE:
+ stmttype = "close";
+ break;
default:
stmttype = "unknown";
break;
@@ -587,6 +641,11 @@
copy_rec((PLpgSQL_rec *) (func->datums[i]));
break;
+ case PLPGSQL_DTYPE_CURSOR:
+ estate.datums[i] = (PLpgSQL_datum *)
+ copy_cursor((PLpgSQL_cursor *) (func->datums[i]));
+ break;
+
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_TRIGARG:
@@ -713,6 +772,16 @@
}
break;
+ case PLPGSQL_DTYPE_CURSOR:
+ {
+ PLpgSQL_cursor *cur = (PLpgSQL_cursor *) estate.datums[i];
+
+ cur->tuptable = NULL;
+ cur->count = 0;
+ cur->index = -1;
+ }
+ break;
+
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_REC:
case PLPGSQL_DTYPE_RECFIELD:
@@ -809,7 +878,17 @@
return new;
}
+static PLpgSQL_cursor *
+copy_cursor(PLpgSQL_cursor * cursor)
+{
+ PLpgSQL_cursor *new = palloc(sizeof(PLpgSQL_cursor));
+
+ memcpy(new, cursor, sizeof(PLpgSQL_cursor));
+
+ return new;
+}
+
/* ----------
* exec_stmt_block Execute a block of statements
* ----------
@@ -861,6 +940,16 @@
}
break;
+ case PLPGSQL_DTYPE_CURSOR:
+ {
+ PLpgSQL_cursor *cur = (PLpgSQL_cursor *) estate->datums[n];
+
+ cur->tuptable = NULL;
+ cur->count = 0;
+ cur->index = -1;
+ }
+ break;
+
case PLPGSQL_DTYPE_RECFIELD:
break;
@@ -974,6 +1063,10 @@
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_SELECT:
rc = exec_stmt_select(estate, (PLpgSQL_stmt_select *) stmt);
break;
@@ -1002,6 +1095,18 @@
rc = exec_stmt_dynfors(estate, (PLpgSQL_stmt_dynfors *) stmt);
break;
+ case PLPGSQL_STMT_OPEN:
+ rc = exec_stmt_open(estate, (PLpgSQL_stmt_open *) stmt);
+ break;
+
+ case PLPGSQL_STMT_FETCH:
+ rc = exec_stmt_fetch(estate, (PLpgSQL_stmt_fetch *) stmt);
+ break;
+
+ case PLPGSQL_STMT_CLOSE:
+ rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt);
+ break;
+
default:
error_info_stmt = save_estmt;
elog(ERROR, "unknown cmdtype %d in exec_stmt",
@@ -1305,17 +1410,17 @@
/* ----------
- * exec_stmt_fors Execute a query, assign each
+ * exec_stmt_forsc Execute a query, assign each
* tuple to a record or row and
* execute a group of statements
* for it.
* ----------
*/
static int
-exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
+exec_stmt_forsc(PLpgSQL_execstate *estate, char *label, PLpgSQL_rec *rec,
+ PLpgSQL_row *row, PLpgSQL_expr *query,
+ PLpgSQL_stmts *body, int skip)
{
- PLpgSQL_rec *rec = NULL;
- PLpgSQL_row *row = NULL;
SPITupleTable *tuptab;
int rc;
int i;
@@ -1327,22 +1432,9 @@
exec_set_found(estate, 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 in exec_stmt_fors()");
- }
-
- /*
* Run the query
*/
- exec_run_select(estate, stmt->query, 0);
+ exec_run_select(estate, query, 0);
n = SPI_processed;
/*
@@ -1351,7 +1443,7 @@
*/
if (n == 0)
{
- exec_move_row(estate, rec, row, NULL, NULL);
+ exec_move_row(estate, rec, row, NULL, NULL, 0);
return PLPGSQL_RC_OK;
}
@@ -1372,12 +1464,13 @@
/*
* Assign the tuple to the target
*/
- exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
+ exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc,
+ skip);
/*
* Execute the statements
*/
- rc = exec_stmts(estate, stmt->body);
+ rc = exec_stmts(estate, body);
/*
* Check returncode
@@ -1390,9 +1483,9 @@
case PLPGSQL_RC_EXIT:
if (estate->exitlabel == NULL)
return PLPGSQL_RC_OK;
- if (stmt->label == NULL)
+ if (label == NULL)
return PLPGSQL_RC_EXIT;
- if (strcmp(stmt->label, estate->exitlabel))
+ if (strcmp(label, estate->exitlabel))
return PLPGSQL_RC_EXIT;
estate->exitlabel = NULL;
return PLPGSQL_RC_OK;
@@ -1410,6 +1503,83 @@
/* ----------
+ * exec_stmt_fors Execute a query, assign each
+ * tuple to a record or row and
+ * execute a group of statements
+ * for it.
+ * ----------
+ */
+static int
+exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
+{
+ PLpgSQL_rec *rec = NULL;
+ PLpgSQL_row *row = NULL;
+
+ /* ----------
+ * 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 in exec_stmt_fors()");
+ }
+
+ return exec_stmt_forsc(estate, stmt->label, rec, row, stmt->query,
+ stmt->body, 0);
+}
+
+/* ----------
+ * exec_stmt_forc Evaluate a cursor, assign each
+ * tuple to a record or row and
+ * execute a group of statements
+ * for it.
+ * ----------
+ */
+static int
+exec_stmt_forc(PLpgSQL_execstate * estate, PLpgSQL_stmt_forc * stmt)
+{
+ PLpgSQL_rec *rec = NULL;
+ PLpgSQL_row *row = NULL;
+ PLpgSQL_var *var;
+ int ret;
+
+ /* ----------
+ * 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 in exec_stmt_forc()");
+ }
+
+ exec_set_cursor_params(estate, stmt->cursor, stmt->nparams, stmt->params);
+ if (stmt->cursor->saw_current_of && ! stmt->cursor->oid_added)
+ exec_cursor_add_oid(stmt->cursor);
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->isopen_varno]);
+ var->value = (Datum) true;
+ var->isnull = false;
+
+ ret = exec_stmt_forsc(estate, stmt->label, rec, row,
+ stmt->cursor->select, stmt->body,
+ stmt->cursor->oid_added ? 1 : 0);
+
+ var->value = (Datum) false;
+
+ return ret;
+}
+
+/* ----------
* exec_stmt_select Run a query and assign the first
* row to a record or rowtype.
* ----------
@@ -1452,7 +1622,7 @@
*/
if (n == 0)
{
- exec_move_row(estate, rec, row, NULL, NULL);
+ exec_move_row(estate, rec, row, NULL, NULL, 0);
return PLPGSQL_RC_OK;
}
@@ -1462,7 +1632,7 @@
tuptab = SPI_tuptable;
SPI_tuptable = NULL;
- exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
+ exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc, 0);
exec_set_found(estate, true);
@@ -2060,7 +2230,7 @@
*/
if (n == 0)
{
- exec_move_row(estate, rec, row, NULL, NULL);
+ exec_move_row(estate, rec, row, NULL, NULL, 0);
return PLPGSQL_RC_OK;
}
@@ -2081,7 +2251,7 @@
/*
* Assign the tuple to the target
*/
- exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
+ exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc, 0);
/*
* Execute the statements
@@ -2119,10 +2289,228 @@
/* ----------
- * exec_assign_expr Put an expressions result into
- * a variable.
+ * exec_stmt_open Execute an OPEN statement.
* ----------
*/
+static int
+exec_stmt_open(PLpgSQL_execstate * estate,
+ PLpgSQL_stmt_open * stmt)
+{
+ PLpgSQL_var *var;
+
+ if (stmt->cursor->index >= 0)
+ elog(ERROR, "Attempt to open cursor `%s' when it is already open",
+ stmt->cursor->refname);
+
+ exec_set_cursor_params(estate, stmt->cursor, stmt->nparams, stmt->params);
+ if (stmt->cursor->saw_current_of && ! stmt->cursor->oid_added)
+ exec_cursor_add_oid(stmt->cursor);
+
+ exec_set_found(estate, false);
+
+ /* If and when SPI supports cursors, we should use a SQL cursor
+ * here, rather than doing the whole query at once.
+ */
+
+ exec_run_select(estate, stmt->cursor->select, 0);
+ stmt->cursor->tuptable = SPI_tuptable;
+ stmt->cursor->count = SPI_processed;
+ SPI_tuptable = NULL;
+
+ stmt->cursor->index = 0;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->found_varno]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->isopen_varno]);
+ var->value = (Datum) true;
+ var->isnull = false;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->notfound_varno]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->rowcount_varno]);
+ var->value = Int32GetDatum(0);
+ var->isnull = false;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->oid_varno]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ if (SPI_processed > 0)
+ exec_set_found(estate, true);
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_fetch Execute a FETCH statement.
+ * ----------
+ */
+static int
+exec_stmt_fetch(PLpgSQL_execstate * estate,
+ PLpgSQL_stmt_fetch * stmt)
+{
+ PLpgSQL_var *var;
+ HeapTuple tup;
+ TupleDesc tupdesc;
+ int skip;
+
+ if (stmt->cursor->index < 0)
+ elog(ERROR, "FETCH from closed cursor");
+
+ if (stmt->cursor->index >= stmt->cursor->count)
+ {
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->found_varno]);
+ var->value = (Datum) false;
+ var->isnull = false;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->notfound_varno]);
+ var->value = (Datum) true;
+ var->isnull = false;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->oid_varno]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ return PLPGSQL_RC_OK;
+ }
+
+ tup = stmt->cursor->tuptable->vals[stmt->cursor->index];
+ tupdesc = stmt->cursor->tuptable->tupdesc;
+
+ skip = stmt->cursor->oid_added ? 1 : 0;
+
+ if (stmt->nvars == 0)
+ {
+ PLpgSQL_rec *rec = NULL;
+ PLpgSQL_row *row = NULL;
+
+ 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 in exec_stmt_fetch()");
+ }
+
+ exec_move_row(estate, rec, row, tup, tupdesc, skip);
+ }
+ else
+ {
+ int i;
+
+ if (! HeapTupleIsValid(tup))
+ {
+ /* Can this happen? I'm not sure. */
+ for (i = 0; i < stmt->nvars; ++i)
+ {
+ int varno;
+ bool nullval = true;
+
+ varno = stmt->varnos[i];
+ exec_assign_value(estate, estate->datums[varno],
+ (Datum) 0, 0, &nullval);
+ }
+ }
+ else
+ {
+ if (stmt->nvars != tup->t_data->t_natts - skip)
+ elog(ERROR,
+ "Number of elements in FETCH (%d) does not match number of target variables (%d)",
+ tup->t_data->t_natts,
+ stmt->nvars);
+
+ for (i = 0; i < stmt->nvars; ++i)
+ {
+ Datum value;
+ Oid valtype;
+ bool isnull;
+
+ value = SPI_getbinval(tup, tupdesc, i + 1 + skip, &isnull);
+ valtype = SPI_gettypeid(tupdesc, i + 1 + skip);
+ exec_assign_value(estate, estate->datums[stmt->varnos[i]],
+ value, valtype, &isnull);
+ }
+ }
+ }
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->found_varno]);
+ var->value = (Datum) true;
+ var->isnull = false;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->notfound_varno]);
+ var->value = (Datum) false;
+ var->isnull = false;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->rowcount_varno]);
+ var->value = Int32GetDatum(DatumGetInt32(var->value) + 1);
+
+ if (stmt->cursor->oid_added)
+ {
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->oid_varno]);
+ var->value = SPI_getbinval(tup, tupdesc, 1, &var->isnull);
+ elog(DEBUG, "exec_stmt_fetch: OID is %d", DatumGetInt32(var->value));
+ }
+
+ ++stmt->cursor->index;
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_close Execute a CLOSE statement.
+ * ----------
+ */
+static int
+exec_stmt_close(PLpgSQL_execstate * estate,
+ PLpgSQL_stmt_close * stmt)
+{
+ PLpgSQL_var *var;
+
+ /* We could free the tuple table here if we know how. If and when
+ * SPI supports cursors, and OPEN and FETCH are changed to use
+ * cursors, then this is where we would close the cursor.
+ */
+
+ stmt->cursor->tuptable = NULL;
+ stmt->cursor->count = 0;
+ stmt->cursor->index = -1;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->found_varno]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->isopen_varno]);
+ var->value = (Datum) false;
+ var->isnull = false;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->notfound_varno]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->rowcount_varno]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->cursor->oid_varno]);
+ var->value = (Datum) 0;
+ var->isnull = true;
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_assign_expr Put an expressions result into
+ * a variable.
+ * ---------- */
static void
exec_assign_expr(PLpgSQL_execstate * estate, PLpgSQL_datum * target,
PLpgSQL_expr * expr)
@@ -2577,7 +2965,7 @@
exec_move_row(PLpgSQL_execstate * estate,
PLpgSQL_rec * rec,
PLpgSQL_row * row,
- HeapTuple tup, TupleDesc tupdesc)
+ HeapTuple tup, TupleDesc tupdesc, int skip)
{
PLpgSQL_var *var;
int i;
@@ -2591,6 +2979,10 @@
*/
if (rec != NULL)
{
+ /* We go ahead and do this even if skip != 0. The tuple
+ * descriptor will let the user pick out the right fields
+ * anyhow.
+ */
if (HeapTupleIsValid(tup))
{
rec->tup = tup;
@@ -2614,7 +3006,7 @@
{
if (HeapTupleIsValid(tup))
{
- if (row->nfields != tup->t_data->t_natts)
+ if (row->nfields != tup->t_data->t_natts - skip)
{
elog(ERROR, "query didn't return correct # of attributes for %s",
row->refname);
@@ -2624,8 +3016,8 @@
{
var = (PLpgSQL_var *) (estate->datums[row->varnos[i]]);
- valtype = SPI_gettypeid(tupdesc, i + 1);
- value = SPI_getbinval(tup, tupdesc, i + 1, &isnull);
+ valtype = SPI_gettypeid(tupdesc, i + 1 + skip);
+ value = SPI_getbinval(tup, tupdesc, i + 1 + skip, &isnull);
exec_assign_value(estate, estate->datums[row->varnos[i]],
value, valtype, &isnull);
@@ -2834,4 +3226,96 @@
var = (PLpgSQL_var *) (estate->datums[estate->found_varno]);
var->value = (Datum) state;
var->isnull = false;
+}
+
+
+/* ----------
+ * exec_set_cursor_params Set cursor parameters
+ * ----------
+ */
+static void
+exec_set_cursor_params(PLpgSQL_execstate * estate, PLpgSQL_cursor * cursor,
+ int nparams, PLpgSQL_expr ** params)
+{
+ int i;
+
+ for (i = 0; i < cursor->n_params; ++i)
+ {
+ int n;
+ PLpgSQL_var *var;
+
+ n = cursor->params[i];
+
+ if (estate->datums[n]->dtype != PLPGSQL_DTYPE_VAR)
+ elog(ERROR, "unknown dtype %d in exec_set_cursor_params()",
+ estate->datums[n]->dtype);
+
+ var = (PLpgSQL_var *) (estate->datums[n]);
+
+ if (i < nparams)
+ exec_assign_expr(estate, (PLpgSQL_datum *) var, params[i]);
+ else if (var->default_val != NULL)
+ exec_assign_expr(estate, (PLpgSQL_datum *) var, var->default_val);
+ else
+ {
+ var->value = (Datum) 0;
+ var->isnull = true;
+ if (var->notnull)
+ elog(ERROR,
+ "cursor parameter '%s' declared NOT NULL cannot default to NULL",
+ var->refname);
+ }
+ }
+}
+
+/* ----------
+ * exec_cursor_add_oid If CURRENT OF is used with a cursor,
+ * add oid as the first field that we retrieve.
+ * ----------
+ */
+static void
+exec_cursor_add_oid(PLpgSQL_cursor * cursor)
+{
+ char *s;
+ char *new;
+
+ if (! cursor->saw_current_of || cursor->oid_added)
+ return;
+
+ s = cursor->select->query;
+ if (strncasecmp(s, "select ", 7) != 0)
+ elog(ERROR, "exec_cursor_add_oid: internal error: bad start");
+ s += 7;
+
+ if (strncasecmp(s, "distinct on ", 12) == 0)
+ {
+ s = strchr(s, ')');
+ if (s == NULL)
+ elog(ERROR, "syntax error in select distinct on clause");
+ ++s;
+ if (*s == ' ')
+ ++s;
+ }
+ else if (strncasecmp(s, "distinct ", 9) == 0)
+ s += 9;
+ else if (strncasecmp(s, "all ", 4) == 0)
+ s += 4;
+
+ /* This is too simple, because if the select is over multiple
+ * tables, oid will be ambiguous. In that case, we need to figure
+ * out which tables the select is over, and add an oid field for
+ * each one. This is also too simple in that it does not permit
+ * UNION, etc. If we ever fix this, note that we only need the
+ * oid for tables which are selected FOR UPDATE.
+ */
+
+ new = malloc(strlen(cursor->select->query) + 10);
+ strncpy(new, cursor->select->query, s - cursor->select->query);
+ strcpy(new + (s - cursor->select->query), "oid, ");
+ strcat(new, s);
+
+ free(cursor->select->query);
+ cursor->select->query = new;
+
+ cursor->oid_added = true;
}
Index: src/pl/plpgsql/src/pl_funcs.c
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v
retrieving revision 1.12
diff -u -r1.12 pl_funcs.c
--- src/pl/plpgsql/src/pl_funcs.c 2001/03/22 06:16:21 1.12
+++ src/pl/plpgsql/src/pl_funcs.c 2001/03/25 01:14:51
@@ -379,6 +379,7 @@
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_select(PLpgSQL_stmt_select * stmt);
static void dump_exit(PLpgSQL_stmt_exit * stmt);
static void dump_return(PLpgSQL_stmt_return * stmt);
@@ -386,6 +387,9 @@
static void dump_execsql(PLpgSQL_stmt_execsql * stmt);
static void dump_dynexecute(PLpgSQL_stmt_dynexecute * stmt);
static void dump_dynfors(PLpgSQL_stmt_dynfors * stmt);
+static void dump_open(PLpgSQL_stmt_open * stmt);
+static void dump_fetch(PLpgSQL_stmt_fetch * stmt);
+static void dump_close(PLpgSQL_stmt_close * stmt);
static void dump_getdiag(PLpgSQL_stmt_getdiag * stmt);
static void dump_expr(PLpgSQL_expr * expr);
@@ -426,6 +430,9 @@
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_SELECT:
dump_select((PLpgSQL_stmt_select *) stmt);
break;
@@ -447,6 +454,15 @@
case PLPGSQL_STMT_DYNFORS:
dump_dynfors((PLpgSQL_stmt_dynfors *) stmt);
break;
+ case PLPGSQL_STMT_OPEN:
+ dump_open((PLpgSQL_stmt_open *) stmt);
+ break;
+ case PLPGSQL_STMT_FETCH:
+ dump_fetch((PLpgSQL_stmt_fetch *) stmt);
+ break;
+ case PLPGSQL_STMT_CLOSE:
+ dump_close((PLpgSQL_stmt_close *) stmt);
+ break;
case PLPGSQL_STMT_GETDIAG:
dump_getdiag((PLpgSQL_stmt_getdiag *) stmt);
break;
@@ -597,6 +613,39 @@
}
static void
+dump_forc(PLpgSQL_stmt_forc * stmt)
+{
+ int i;
+
+ dump_ind();
+ printf("FORC %s IN %s",
+ (stmt->rec != NULL) ? stmt->rec->refname : stmt->row->refname,
+ stmt->cursor->refname);
+ if (stmt->nparams > 0)
+ {
+ int i;
+
+ printf("(");
+ for (i = 0; i < stmt->nparams; ++i)
+ {
+ if (i > 0)
+ printf(", ");
+ dump_expr(stmt->params[i]);
+ }
+ printf(")");
+ }
+ printf("\n");
+
+ dump_indent += 2;
+ for (i = 0; i < stmt->body->stmts_used; i++)
+ dump_stmt((PLpgSQL_stmt *) (stmt->body->stmts[i]));
+ dump_indent -= 2;
+
+ dump_ind();
+ printf(" ENDFORC\n");
+}
+
+static void
dump_select(PLpgSQL_stmt_select * stmt)
{
dump_ind();
@@ -696,6 +745,59 @@
dump_ind();
printf(" ENDFORS\n");
+}
+
+static void
+dump_open(PLpgSQL_stmt_open * stmt)
+{
+ dump_ind();
+ printf("OPEN %s", stmt->cursor->refname);
+ if (stmt->nparams > 0)
+ {
+ int i;
+
+ printf("(");
+ for (i = 0; i < stmt->nparams; ++i)
+ {
+ if (i > 0)
+ printf(", ");
+ dump_expr(stmt->params[i]);
+ }
+ printf(")");
+ }
+ printf("\n");
+}
+
+static void
+dump_fetch(PLpgSQL_stmt_fetch * stmt)
+{
+ printf("FETCH %s INTO", stmt->cursor->refname);
+ if (stmt->nvars == 0)
+ {
+ if (stmt->rec != NULL)
+ printf(" %s", stmt->rec->refname);
+ else
+ printf(" %s", stmt->row->refname);
+ }
+ else
+ {
+ int i;
+
+ for (i = 0; i < stmt->nvars; ++i)
+ {
+ if (i > 0)
+ printf(",");
+ printf(" var %d", stmt->varnos[i]);
+ }
+ }
+ printf("\n");
+}
+
+static void
+dump_close(PLpgSQL_stmt_close * stmt)
+{
+ dump_ind();
+ printf("CLOSE %s\n", stmt->cursor->refname);
}
static void
Index: src/pl/plpgsql/src/plpgsql.h
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v
retrieving revision 1.13
diff -u -r1.13 plpgsql.h
--- src/pl/plpgsql/src/plpgsql.h 2001/03/22 04:01:42 1.13
+++ src/pl/plpgsql/src/plpgsql.h 2001/03/25 01:14:51
@@ -57,7 +57,8 @@
PLPGSQL_NSTYPE_VAR,
PLPGSQL_NSTYPE_ROW,
PLPGSQL_NSTYPE_REC,
- PLPGSQL_NSTYPE_RECFIELD
+ PLPGSQL_NSTYPE_RECFIELD,
+ PLPGSQL_NSTYPE_CURSOR
};
/* ----------
@@ -71,7 +72,8 @@
PLPGSQL_DTYPE_REC,
PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_EXPR,
- PLPGSQL_DTYPE_TRIGARG
+ PLPGSQL_DTYPE_TRIGARG,
+ PLPGSQL_DTYPE_CURSOR
};
/* ----------
@@ -87,6 +89,7 @@
PLPGSQL_STMT_WHILE,
PLPGSQL_STMT_FORI,
PLPGSQL_STMT_FORS,
+ PLPGSQL_STMT_FORC,
PLPGSQL_STMT_SELECT,
PLPGSQL_STMT_EXIT,
PLPGSQL_STMT_RETURN,
@@ -94,6 +97,9 @@
PLPGSQL_STMT_EXECSQL,
PLPGSQL_STMT_DYNEXECUTE,
PLPGSQL_STMT_DYNFORS,
+ PLPGSQL_STMT_OPEN,
+ PLPGSQL_STMT_FETCH,
+ PLPGSQL_STMT_CLOSE,
PLPGSQL_STMT_GETDIAG
};
@@ -227,6 +233,31 @@
typedef struct
+{ /* Cursor */
+ int dtype;
+ int cursorno;
+ char *refname;
+ int lineno;
+
+ PLpgSQL_expr *select;
+ int n_params;
+ int *params;
+
+ int found_varno;
+ int isopen_varno;
+ int notfound_varno;
+ int rowcount_varno;
+ int oid_varno;
+ bool saw_current_of;
+ bool oid_added;
+
+ SPITupleTable *tuptable;
+ int count;
+ int index; /* Index in tuptable; -1 if cursor closed */
+} PLpgSQL_cursor;
+
+
+typedef struct
{ /* Item in the compilers namestack */
int itemtype;
int itemno;
@@ -360,6 +391,20 @@
typedef struct
+{ /* FOR statement running over cursor */
+ int cmd_type;
+ int lineno;
+ char *label;
+ PLpgSQL_rec *rec;
+ PLpgSQL_row *row;
+ PLpgSQL_cursor *cursor;
+ int nparams;
+ PLpgSQL_expr **params;
+ PLpgSQL_stmts *body;
+} PLpgSQL_stmt_forc;
+
+
+typedef struct
{ /* SELECT ... INTO statement */
int cmd_type;
int lineno;
@@ -415,6 +460,36 @@
} PLpgSQL_stmt_dynexecute;
+typedef struct
+{ /* OPEN statement */
+ int cmd_type;
+ int lineno;
+ PLpgSQL_cursor *cursor;
+ int nparams;
+ PLpgSQL_expr **params;
+} PLpgSQL_stmt_open;
+
+
+typedef struct
+{ /* FETCH statement */
+ int cmd_type;
+ int lineno;
+ PLpgSQL_cursor *cursor;
+ int nvars;
+ int *varnos;
+ PLpgSQL_rec *rec;
+ PLpgSQL_row *row;
+} PLpgSQL_stmt_fetch;
+
+
+typedef struct
+{ /* CLOSE statement */
+ int cmd_type;
+ int lineno;
+ PLpgSQL_cursor *cursor;
+} PLpgSQL_stmt_close;
+
+
typedef struct PLpgSQL_function
{ /* Complete compiled function */
Oid fn_oid;
@@ -497,6 +572,7 @@
extern int plpgsql_parse_wordtype(char *string);
extern int plpgsql_parse_dblwordtype(char *string);
extern int plpgsql_parse_wordrowtype(char *string);
+extern int plpgsql_parse_attribute(char *string);
extern void plpgsql_adddatum(PLpgSQL_datum * new);
extern int plpgsql_add_initdatums(int **varnos);
extern void plpgsql_comperrinfo(void);
@@ -553,6 +629,7 @@
extern void plpgsql_yyrestart(FILE *fp);
extern int plpgsql_yylex(void);
extern void plpgsql_setinput(char *s, int functype);
+extern void plpgsql_unput(char);
extern int plpgsql_yyparse(void);
extern void plpgsql_yyerror(const char *s);
Index: src/pl/plpgsql/src/scan.l
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/pl/plpgsql/src/scan.l,v
retrieving revision 1.9
diff -u -r1.9 scan.l
--- src/pl/plpgsql/src/scan.l 2001/02/19 19:49:53 1.9
+++ src/pl/plpgsql/src/scan.l 2001/03/25 01:14:51
@@ -48,7 +48,6 @@
static void plpgsql_input(char *buf, int *result, int max);
#define YY_INPUT(buf,res,max) plpgsql_input(buf, &res, max)
-#define YY_NO_UNPUT
%}
WS [\200-\377_A-Za-z"]
@@ -93,7 +92,9 @@
begin { return K_BEGIN; }
bpchar { return T_BPCHAR; }
char { return T_CHAR; }
+close { return K_CLOSE; }
constant { return K_CONSTANT; }
+cursor { return K_CURSOR; }
debug { return K_DEBUG; }
declare { return K_DECLARE; }
default { return K_DEFAULT; }
@@ -103,6 +104,7 @@
exception { return K_EXCEPTION; }
execute { return K_EXECUTE; }
exit { return K_EXIT; }
+fetch { return K_FETCH; }
for { return K_FOR; }
from { return K_FROM; }
get { return K_GET; }
@@ -113,6 +115,7 @@
not { return K_NOT; }
notice { return K_NOTICE; }
null { return K_NULL; }
+open { return K_OPEN; }
perform { return K_PERFORM; }
raise { return K_RAISE; }
record { return K_RECORD; }
@@ -143,6 +146,10 @@
{WS}{WC}*%TYPE { return plpgsql_parse_wordtype(yytext); }
{WS}{WC}*\.{WS}{WC}*%TYPE { return plpgsql_parse_dblwordtype(yytext); }
{WS}{WC}*%ROWTYPE { return plpgsql_parse_wordrowtype(yytext); }
+{WS}{WC}*%FOUND { return plpgsql_parse_attribute(yytext); }
+{WS}{WC}*%ISOPEN { return plpgsql_parse_attribute(yytext); }
+{WS}{WC}*%NOTFOUND { return plpgsql_parse_attribute(yytext); }
+{WS}{WC}*%ROWCOUNT { return plpgsql_parse_attribute(yytext); }
\$[0-9]+ { return plpgsql_parse_word(yytext); }
[0-9]+ { return T_NUMBER; }
@@ -249,4 +256,11 @@
scanner_functype = functype;
scanner_typereported = 0;
+}
+
+
+void
+plpgsql_unput(char c)
+{
+ unput(c);
}
Index: src/test/regress/expected/plpgsql.out
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/test/regress/expected/plpgsql.out,v
retrieving revision 1.5
diff -u -r1.5 plpgsql.out
--- src/test/regress/expected/plpgsql.out 2000/10/22 23:32:45 1.5
+++ src/test/regress/expected/plpgsql.out 2001/03/25 01:14:53
@@ -1515,3 +1515,109 @@
ERROR: system "notthere" does not exist
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
ERROR: IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max)
+--
+-- Test cursors
+--
+create function test_cursors()
+returns text as '
+declare
+ cursor mycursor(slot char(20)) is
+ select comment from PLine where slotname = slot;
+ crec mycursor%ROWTYPE;
+ srec record;
+ rcount integer;
+ ctext text;
+ cursor allcursor is select * from PLine;
+ allrec allcursor%ROWTYPE;
+ dcount integer;
+begin
+ rcount := 0;
+ for srec in select comment from PLine where slotname = ''PL.001'' loop
+ if rcount != 0 then
+ raise exception ''test_cursors: too many records in for select'';
+ end if;
+ rcount := rcount + 1;
+ if srec.comment != ''Central call'' then
+ raise exception ''test_cursors: bad comment in for select'';
+ end if;
+ end loop;
+
+ rcount := 0;
+ if mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor should not be open'';
+ end if;
+ for crec in mycursor(''PL.001'') loop
+ if rcount != 0 then
+ raise exception ''test_cursors: too many records'';
+ end if;
+ rcount := rcount + 1;
+
+ if not mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor should be open'';
+ end if;
+
+ if crec.comment != ''Central call'' then
+ raise exception ''test_cursors:: bad comment'';
+ end if;
+ end loop;
+
+ if mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor should not be open'';
+ end if;
+ open mycursor(''PL.001'');
+ if not mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor should be open'';
+ end if;
+
+ fetch mycursor into crec;
+ if not mycursor%FOUND then
+ raise exception ''test_cursors: first record not found 1'';
+ end if;
+ if mycursor%NOTFOUND then
+ raise exception ''test_cursors: first record not found 2'';
+ end if;
+
+ if crec.comment != ''Central call'' then
+ raise exception ''test_cursors:: bad comment after fetch'';
+ end if;
+
+ update PLine set comment = ''Central call number''
+ where current of mycursor;
+
+ fetch mycursor into crec;
+ if mycursor%FOUND then
+ raise exception ''test_cursors: second record found'';
+ end if;
+ if mycursor%ROWCOUNT != 1 then
+ raise exception ''test_cursors: bad row count'';
+ end if;
+
+ close mycursor;
+ if mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor open after close'';
+ end if;
+
+ select into ctext comment from PLine where slotname = ''PL.001'';
+ if ctext != ''Central call number'' then
+ raise exception ''test_cursors: modification failed'';
+ end if;
+
+ rcount := 0;
+ for allrec in allcursor loop
+ rcount := rcount + 1;
+ end loop;
+ select into dcount count(*) from Pline;
+ if rcount != dcount then
+ raise exception ''test_cursors: count mismatch % != %'',
+ rcount, dcount;
+ end if;
+
+ return ''ok'';
+end;
+' language 'plpgsql';
+select test_cursors();
+ test_cursors
+--------------
+ ok
+(1 row)
+
Index: src/test/regress/sql/plpgsql.sql
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/test/regress/sql/plpgsql.sql,v
retrieving revision 1.4
diff -u -r1.4 plpgsql.sql
--- src/test/regress/sql/plpgsql.sql 2000/10/22 23:32:46 1.4
+++ src/test/regress/sql/plpgsql.sql 2001/03/25 01:14:53
@@ -1399,3 +1399,106 @@
insert into IFace values ('IF', 'notthere', 'eth0', '');
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
+--
+-- Test cursors
+--
+
+create function test_cursors()
+returns text as '
+declare
+ cursor mycursor(slot char(20)) is
+ select comment from PLine where slotname = slot;
+ crec mycursor%ROWTYPE;
+ srec record;
+ rcount integer;
+ ctext text;
+ cursor allcursor is select * from PLine;
+ allrec allcursor%ROWTYPE;
+ dcount integer;
+begin
+ rcount := 0;
+ for srec in select comment from PLine where slotname = ''PL.001'' loop
+ if rcount != 0 then
+ raise exception ''test_cursors: too many records in for select'';
+ end if;
+ rcount := rcount + 1;
+ if srec.comment != ''Central call'' then
+ raise exception ''test_cursors: bad comment in for select'';
+ end if;
+ end loop;
+
+ rcount := 0;
+ if mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor should not be open'';
+ end if;
+ for crec in mycursor(''PL.001'') loop
+ if rcount != 0 then
+ raise exception ''test_cursors: too many records'';
+ end if;
+ rcount := rcount + 1;
+
+ if not mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor should be open'';
+ end if;
+
+ if crec.comment != ''Central call'' then
+ raise exception ''test_cursors:: bad comment'';
+ end if;
+ end loop;
+
+ if mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor should not be open'';
+ end if;
+ open mycursor(''PL.001'');
+ if not mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor should be open'';
+ end if;
+
+ fetch mycursor into crec;
+ if not mycursor%FOUND then
+ raise exception ''test_cursors: first record not found 1'';
+ end if;
+ if mycursor%NOTFOUND then
+ raise exception ''test_cursors: first record not found 2'';
+ end if;
+
+ if crec.comment != ''Central call'' then
+ raise exception ''test_cursors:: bad comment after fetch'';
+ end if;
+
+ update PLine set comment = ''Central call number''
+ where current of mycursor;
+
+ fetch mycursor into crec;
+ if mycursor%FOUND then
+ raise exception ''test_cursors: second record found'';
+ end if;
+ if mycursor%ROWCOUNT != 1 then
+ raise exception ''test_cursors: bad row count'';
+ end if;
+
+ close mycursor;
+ if mycursor%ISOPEN then
+ raise exception ''test_cursors: cursor open after close'';
+ end if;
+
+ select into ctext comment from PLine where slotname = ''PL.001'';
+ if ctext != ''Central call number'' then
+ raise exception ''test_cursors: modification failed'';
+ end if;
+
+ rcount := 0;
+ for allrec in allcursor loop
+ rcount := rcount + 1;
+ end loop;
+ select into dcount count(*) from Pline;
+ if rcount != dcount then
+ raise exception ''test_cursors: count mismatch % != %'',
+ rcount, dcount;
+ end if;
+
+ return ''ok'';
+end;
+' language 'plpgsql';
+
+select test_cursors();