OSDN Git Service

Allow MOVE FORWARD n, MOVE BACKWARD n, MOVE FORWARD ALL, MOVE BACKWARD ALL
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 29 Sep 2009 20:05:29 +0000 (20:05 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 29 Sep 2009 20:05:29 +0000 (20:05 +0000)
in plpgsql.  Clean up a couple of corner cases in the MOVE/FETCH syntax.

Pavel Stehule

doc/src/sgml/plpgsql.sgml
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/plpgsql.h
src/pl/plpgsql/src/scan.l
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index f430bcf..65d508c 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.142 2009/06/18 10:22:08 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.143 2009/09/29 20:05:29 tgl Exp $ -->
 
 <chapter id="plpgsql">
   <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@@ -2655,16 +2655,18 @@ MOVE <optional> <replaceable>direction</replaceable> { FROM | IN } </optional> <
     </para>
 
     <para>
-     The options for the <replaceable>direction</replaceable> clause are
-     the same as for <command>FETCH</>, namely
+     The <replaceable>direction</replaceable> clause can be any of the
+     variants allowed in the SQL <xref linkend="sql-fetch"
+     endterm="sql-fetch-title"> command, namely
      <literal>NEXT</>,
      <literal>PRIOR</>,
      <literal>FIRST</>,
      <literal>LAST</>,
      <literal>ABSOLUTE</> <replaceable>count</replaceable>,
      <literal>RELATIVE</> <replaceable>count</replaceable>,
-     <literal>FORWARD</>, or
-     <literal>BACKWARD</>.
+     <literal>ALL</>,
+     <literal>FORWARD</> <optional> <replaceable>count</replaceable> | <literal>ALL</> </optional>, or
+     <literal>BACKWARD</> <optional> <replaceable>count</replaceable> | <literal>ALL</> </optional>.
      Omitting <replaceable>direction</replaceable> is the same
      as specifying <literal>NEXT</>.
      <replaceable>direction</replaceable> values that require moving
@@ -2678,6 +2680,7 @@ MOVE <optional> <replaceable>direction</replaceable> { FROM | IN } </optional> <
 MOVE curs1;
 MOVE LAST FROM curs3;
 MOVE RELATIVE -2 FROM curs4;
+MOVE FORWARD 2 FROM curs4;
 </programlisting>
        </para>
      </sect3>
index cb9dcbc..c876b87 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.127 2009/07/22 02:31:38 joe Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.128 2009/09/29 20:05:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,6 +48,8 @@ static        PLpgSQL_expr    *read_sql_stmt(const char *sqlstart);
 static PLpgSQL_type    *read_datatype(int tok);
 static PLpgSQL_stmt    *make_execsql_stmt(const char *sqlstart, int lineno);
 static PLpgSQL_stmt_fetch *read_fetch_direction(void);
+static void                     complete_direction(PLpgSQL_stmt_fetch *fetch,
+                                                                                       bool *check_FROM);
 static PLpgSQL_stmt    *make_return_stmt(int lineno);
 static PLpgSQL_stmt    *make_return_next_stmt(int lineno);
 static PLpgSQL_stmt    *make_return_query_stmt(int lineno);
@@ -178,6 +180,7 @@ static List                         *read_raise_options(void);
                 * Keyword tokens
                 */
 %token K_ALIAS
+%token K_ALL
 %token K_ASSIGN
 %token K_BEGIN
 %token K_BY
@@ -1622,6 +1625,15 @@ stmt_fetch               : K_FETCH lno opt_fetch_direction cursor_variable K_INTO
                                                if (yylex() != ';')
                                                        yyerror("syntax error");
 
+                                               /*
+                                                * We don't allow multiple rows in PL/pgSQL's FETCH
+                                                * statement, only in MOVE.
+                                                */
+                                               if (fetch->returns_multiple_rows)
+                                                       ereport(ERROR,
+                                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                                        errmsg("FETCH statement cannot return multiple rows")));
+
                                                fetch->lineno = $2;
                                                fetch->rec              = rec;
                                                fetch->row              = row;
@@ -2252,6 +2264,9 @@ make_execsql_stmt(const char *sqlstart, int lineno)
 }
 
 
+/*
+ * Read FETCH or MOVE direction clause (everything through FROM/IN).
+ */
 static PLpgSQL_stmt_fetch *
 read_fetch_direction(void)
 {
@@ -2269,6 +2284,7 @@ read_fetch_direction(void)
        fetch->direction = FETCH_FORWARD;
        fetch->how_many  = 1;
        fetch->expr      = NULL;
+       fetch->returns_multiple_rows = false;
 
        /*
         * Most of the direction keywords are not plpgsql keywords, so we
@@ -2311,26 +2327,46 @@ read_fetch_direction(void)
                                                                                   NULL);
                check_FROM = false;
        }
+       else if (pg_strcasecmp(yytext, "all") == 0)
+       {
+               fetch->how_many = FETCH_ALL;
+               fetch->returns_multiple_rows = true;
+       }
        else if (pg_strcasecmp(yytext, "forward") == 0)
        {
-               /* use defaults */
+               complete_direction(fetch, &check_FROM);
        }
        else if (pg_strcasecmp(yytext, "backward") == 0)
        {
                fetch->direction = FETCH_BACKWARD;
+               complete_direction(fetch, &check_FROM);
        }
-       else if (tok != T_SCALAR)
+       else if (tok == K_FROM || tok == K_IN)
        {
+               /* empty direction */
+               check_FROM = false;
+       }
+       else if (tok == T_SCALAR)
+       {
+               /* Assume there's no direction clause and tok is a cursor name */
                plpgsql_push_back_token(tok);
-               fetch->expr = read_sql_expression2(K_FROM, K_IN,
-                                                                                  "FROM or IN",
-                                                                                  NULL);
                check_FROM = false;
        }
        else
        {
-               /* Assume there's no direction clause */
+               /*
+                * Assume it's a count expression with no preceding keyword.
+                * Note: we allow this syntax because core SQL does, but we don't
+                * document it because of the ambiguity with the omitted-direction
+                * case.  For instance, "MOVE n IN c" will fail if n is a scalar.
+                * Perhaps this can be improved someday, but it's hardly worth a
+                * lot of work.
+                */
                plpgsql_push_back_token(tok);
+               fetch->expr = read_sql_expression2(K_FROM, K_IN,
+                                                                                  "FROM or IN",
+                                                                                  NULL);
+               fetch->returns_multiple_rows = true;
                check_FROM = false;
        }
 
@@ -2345,6 +2381,43 @@ read_fetch_direction(void)
        return fetch;
 }
 
+/*
+ * Process remainder of FETCH/MOVE direction after FORWARD or BACKWARD.
+ * Allows these cases:
+ *   FORWARD expr,  FORWARD ALL,  FORWARD
+ *   BACKWARD expr, BACKWARD ALL, BACKWARD
+ */
+static void
+complete_direction(PLpgSQL_stmt_fetch *fetch,  bool *check_FROM)
+{
+       int                     tok;
+
+       tok = yylex();
+       if (tok == 0)
+               yyerror("unexpected end of function definition");
+
+       if (tok == K_FROM || tok == K_IN)
+       {
+               *check_FROM = false;
+               return;
+       }
+
+       if (tok == K_ALL)
+       {
+               fetch->how_many = FETCH_ALL;
+               fetch->returns_multiple_rows = true;
+               *check_FROM = true;
+               return;
+       }
+
+       plpgsql_push_back_token(tok);
+       fetch->expr = read_sql_expression2(K_FROM, K_IN,
+                                                                          "FROM or IN",
+                                                                          NULL);
+       fetch->returns_multiple_rows = true;
+       *check_FROM = false;
+}
+
 
 static PLpgSQL_stmt *
 make_return_stmt(int lineno)
@@ -3043,11 +3116,11 @@ make_case(int lineno, PLpgSQL_expr *t_expr,
 
                        /* copy expression query without SELECT keyword (expr->query + 7) */
                        Assert(strncmp(expr->query, "SELECT ", 7) == 0);
-                       
+
                        /* And do the string hacking */
                        initStringInfo(&ds);
 
-                       appendStringInfo(&ds, "SELECT $%d IN(%s)", 
+                       appendStringInfo(&ds, "SELECT $%d IN(%s)",
                                                                nparams + 1,
                                                                expr->query + 7);
 
index cb2cb96..d814e8f 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.80 2009/07/22 02:31:38 joe Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.81 2009/09/29 20:05:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -854,7 +854,7 @@ dump_cursor_direction(PLpgSQL_stmt_fetch *stmt)
                printf("\n");
        }
        else
-               printf("%d\n", stmt->how_many);
+               printf("%ld\n", stmt->how_many);
 
        dump_indent -= 2;
 }
index fb627e7..55ddf54 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.116 2009/09/22 23:43:42 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.117 2009/09/29 20:05:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -517,9 +517,10 @@ typedef struct
        PLpgSQL_row *row;
        int                     curvar;                 /* cursor variable to fetch from */
        FetchDirection direction;       /* fetch direction */
-       int                     how_many;               /* count, if constant (expr is NULL) */
+       long            how_many;               /* count, if constant (expr is NULL) */
        PLpgSQL_expr *expr;                     /* count, if expression */
        bool            is_move;                /* is this a fetch or move? */
+       bool            returns_multiple_rows;  /* can return more than one row? */
 } PLpgSQL_stmt_fetch;
 
 
index 2449779..b309d81 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.71 2009/07/13 00:42:18 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.72 2009/09/29 20:05:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -147,6 +147,7 @@ param                       \${digit}+
 =                              { return K_ASSIGN;                      }
 \.\.                   { return K_DOTDOT;                      }
 alias                  { return K_ALIAS;                       }
+all                            { return K_ALL;                         }
 begin                  { return K_BEGIN;                       }
 by                             { return K_BY;                          }
 case                   { return K_CASE;                        }
index ca4c9dc..a362fb5 100644 (file)
@@ -3027,6 +3027,28 @@ select * from sc_test();
 
 create or replace function sc_test() returns setof integer as $$
 declare
+  c refcursor;
+  x integer;
+begin
+  open c scroll for execute 'select f1 from int4_tbl';
+  fetch last from c into x;
+  while found loop
+    return next x;
+    move backward 2 from c;
+    fetch relative -1 from c into x;
+  end loop;
+  close c;
+end;
+$$ language plpgsql;
+select * from sc_test();
+   sc_test   
+-------------
+ -2147483647
+      123456
+(2 rows)
+
+create or replace function sc_test() returns setof integer as $$
+declare
   c cursor for select * from generate_series(1, 10);
   x integer;
 begin
@@ -3052,6 +3074,26 @@ select * from sc_test();
        9
 (3 rows)
 
+create or replace function sc_test() returns setof integer as $$
+declare
+  c cursor for select * from generate_series(1, 10);
+  x integer;
+begin
+  open c;
+  move forward all in c;
+  fetch backward from c into x;
+  if found then
+    return next x;
+  end if;
+  close c;
+end;
+$$ language plpgsql;
+select * from sc_test();
+ sc_test 
+---------
+      10
+(1 row)
+
 drop function sc_test();
 -- test qualified variable names
 create function pl_qual_names (param1 int) returns void as $$
@@ -3864,7 +3906,7 @@ drop function strtest();
 -- Test anonymous code blocks.
 DO $$
 DECLARE r record;
-BEGIN 
+BEGIN
     FOR r IN SELECT rtrim(roomno) AS roomno, comment FROM Room ORDER BY roomno
     LOOP
         RAISE NOTICE '%, %', r.roomno, r.comment;
@@ -3887,7 +3929,7 @@ LINE 1: DO LANGUAGE plpgsql $$begin return 1; end$$;
                                            ^
 DO LANGUAGE plpgsql $$
 DECLARE r record;
-BEGIN 
+BEGIN
     FOR r IN SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
     LOOP
         RAISE NOTICE '%, %', r.roomno, r.comment;
index 96f8914..80de8eb 100644 (file)
@@ -2513,6 +2513,24 @@ select * from sc_test();
 
 create or replace function sc_test() returns setof integer as $$
 declare
+  c refcursor;
+  x integer;
+begin
+  open c scroll for execute 'select f1 from int4_tbl';
+  fetch last from c into x;
+  while found loop
+    return next x;
+    move backward 2 from c;
+    fetch relative -1 from c into x;
+  end loop;
+  close c;
+end;
+$$ language plpgsql;
+
+select * from sc_test();
+
+create or replace function sc_test() returns setof integer as $$
+declare
   c cursor for select * from generate_series(1, 10);
   x integer;
 begin
@@ -2533,6 +2551,23 @@ $$ language plpgsql;
 
 select * from sc_test();
 
+create or replace function sc_test() returns setof integer as $$
+declare
+  c cursor for select * from generate_series(1, 10);
+  x integer;
+begin
+  open c;
+  move forward all in c;
+  fetch backward from c into x;
+  if found then
+    return next x;
+  end if;
+  close c;
+end;
+$$ language plpgsql;
+
+select * from sc_test();
+
 drop function sc_test();
 
 -- test qualified variable names
@@ -3084,7 +3119,7 @@ drop function strtest();
 
 DO $$
 DECLARE r record;
-BEGIN 
+BEGIN
     FOR r IN SELECT rtrim(roomno) AS roomno, comment FROM Room ORDER BY roomno
     LOOP
         RAISE NOTICE '%, %', r.roomno, r.comment;
@@ -3096,7 +3131,7 @@ DO LANGUAGE plpgsql $$begin return 1; end$$;
 
 DO LANGUAGE plpgsql $$
 DECLARE r record;
-BEGIN 
+BEGIN
     FOR r IN SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
     LOOP
         RAISE NOTICE '%, %', r.roomno, r.comment;