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