]> granicus.if.org Git - postgresql/commitdiff
Reject zero or negative BY step in plpgsql integer FOR-loops, and behave
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 15 Jul 2007 02:15:04 +0000 (02:15 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 15 Jul 2007 02:15:04 +0000 (02:15 +0000)
sanely if the loop value overflows int32 on the way to the end value.
Avoid useless computation of "SELECT 1" when BY is omitted.  Avoid some
type-punning between Datum and int4 that dates from the original coding.

src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/plpgsql.h

index 164a2179a31e78365958016a7f3e9a21bbc0e1f7..0f41c999724b5726fad8710064ecd7df0d74cf56 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.102 2007/04/29 01:21:09 neilc Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.103 2007/07/15 02:15:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -876,7 +876,7 @@ stmt_for            : opt_block_label K_FOR for_control loop_body
                                                }
 
                                                check_labels($1, $4.end_label);
-                                               /* close namespace started in opt_label */
+                                               /* close namespace started in opt_block_label */
                                                plpgsql_ns_pop();
                                        }
                                ;
@@ -968,39 +968,25 @@ for_control               :
                                                                PLpgSQL_stmt_fori       *new;
                                                                char                            *varname;
 
-                                                               /* First expression is well-formed */
+                                                               /* Check first expression is well-formed */
                                                                check_sql_expr(expr1->query);
 
-
-                                                               expr2 = read_sql_construct(K_BY,
-                                                                                                                  K_LOOP,
+                                                               /* Read and check the second one */
+                                                               expr2 = read_sql_construct(K_LOOP,
+                                                                                                                  K_BY,
                                                                                                                   "LOOP",
                                                                                                                   "SELECT ",
                                                                                                                   true,
-                                                                                                                  false,
+                                                                                                                  true,
                                                                                                                   &tok);
 
+                                                               /* Get the BY clause if any */
                                                                if (tok == K_BY)
                                                                        expr_by = plpgsql_read_expression(K_LOOP, "LOOP");
                                                                else
-                                                               {
-                                                                       /*
-                                                                        * If there is no BY clause we will assume 1
-                                                                        */
-                                                                       char buf[1024];
-                                                                       PLpgSQL_dstring         ds;
-
-                                                                       plpgsql_dstring_init(&ds);
-
-                                                                       expr_by = palloc0(sizeof(PLpgSQL_expr));
-                                                                       expr_by->dtype                  = PLPGSQL_DTYPE_EXPR;
-                                                                       strcpy(buf, "SELECT 1");
-                                                                       plpgsql_dstring_append(&ds, buf);
-                                                                       expr_by->query                      = pstrdup(plpgsql_dstring_get(&ds));
-                                                                       expr_by->plan                           = NULL;
-                                                               }
+                                                                       expr_by = NULL;
 
-                                                               /* should have had a single variable name */
+                                                               /* Should have had a single variable name */
                                                                plpgsql_error_lineno = $2.lineno;
                                                                if ($2.scalar && $2.row)
                                                                        ereport(ERROR,
@@ -1023,7 +1009,7 @@ for_control               :
                                                                new->reverse  = reverse;
                                                                new->lower        = expr1;
                                                                new->upper        = expr2;
-                                                               new->by           = expr_by;
+                                                               new->step         = expr_by;
 
                                                                $$ = (PLpgSQL_stmt *) new;
                                                        }
index 2582935452d0150c76ff429476c414f8fa00b4d8..9527fdc61d5aa9c3324acf1ebdf004717c596209 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.197 2007/06/05 21:31:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.198 2007/07/15 02:15:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1517,8 +1517,7 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
 /* ----------
  * exec_stmt_fori                      Iterate an integer variable
  *                                     from a lower to an upper value
- *                                     incrementing or decrementing in BY value
- *                                     Loop can be left with exit.
+ *                                     incrementing or decrementing by the BY value
  * ----------
  */
 static int
@@ -1526,16 +1525,18 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
 {
        PLpgSQL_var *var;
        Datum           value;
-       Datum           by_value;
-       Oid                     valtype;
        bool            isnull;
+       Oid                     valtype;
+       int32           loop_value;
+       int32           end_value;
+       int32           step_value;
        bool            found = false;
        int                     rc = PLPGSQL_RC_OK;
 
        var = (PLpgSQL_var *) (estate->datums[stmt->var->varno]);
 
        /*
-        * Get the value of the lower bound into the loop var
+        * Get the value of the lower bound
         */
        value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype);
        value = exec_cast_value(value, valtype, var->datatype->typoid,
@@ -1546,8 +1547,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                                 errmsg("lower bound of FOR loop cannot be NULL")));
-       var->value = value;
-       var->isnull = false;
+       loop_value = DatumGetInt32(value);
        exec_eval_cleanup(estate);
 
        /*
@@ -1562,22 +1562,32 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
                ereport(ERROR,
                                (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
                                 errmsg("upper bound of FOR loop cannot be NULL")));
+       end_value = DatumGetInt32(value);
        exec_eval_cleanup(estate);
 
        /*
-        * Get the by value
+        * Get the step value
         */
-       by_value = exec_eval_expr(estate, stmt->by, &isnull, &valtype);
-       by_value = exec_cast_value(by_value, valtype, var->datatype->typoid,
-                                                          &(var->datatype->typinput),
-                                                          var->datatype->typioparam,
-                                                          var->datatype->atttypmod, isnull);
-
-       if (isnull)
-               ereport(ERROR,
-                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                                errmsg("by value of FOR loop cannot be NULL")));
-       exec_eval_cleanup(estate);
+       if (stmt->step)
+       {
+               value = exec_eval_expr(estate, stmt->step, &isnull, &valtype);
+               value = exec_cast_value(value, valtype, var->datatype->typoid,
+                                                               &(var->datatype->typinput),
+                                                               var->datatype->typioparam,
+                                                               var->datatype->atttypmod, isnull);
+               if (isnull)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                        errmsg("BY value of FOR loop cannot be NULL")));
+               step_value = DatumGetInt32(value);
+               exec_eval_cleanup(estate);
+               if (step_value <= 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("BY value of FOR loop must be greater than zero")));
+       }
+       else
+               step_value = 1;
 
        /*
         * Now do the loop
@@ -1585,21 +1595,27 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
        for (;;)
        {
                /*
-                * Check bounds
+                * Check against upper bound
                 */
                if (stmt->reverse)
                {
-                       if ((int4) (var->value) < (int4) value)
+                       if (loop_value < end_value)
                                break;
                }
                else
                {
-                       if ((int4) (var->value) > (int4) value)
+                       if (loop_value > end_value)
                                break;
                }
 
                found = true;                   /* looped at least once */
 
+               /*
+                * Assign current value to loop var
+                */
+               var->value = Int32GetDatum(loop_value);
+               var->isnull = false;
+
                /*
                 * Execute the statements
                 */
@@ -1625,13 +1641,12 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
                         * current statement's label, if any: return RC_EXIT so that the
                         * EXIT continues to propagate up the stack.
                         */
-
                        break;
                }
                else if (rc == PLPGSQL_RC_CONTINUE)
                {
                        if (estate->exitlabel == NULL)
-                               /* anonymous continue, so re-run the current loop */
+                               /* unlabelled continue, so re-run the current loop */
                                rc = PLPGSQL_RC_OK;
                        else if (stmt->label != NULL &&
                                         strcmp(stmt->label, estate->exitlabel) == 0)
@@ -1652,12 +1667,21 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
                }
 
                /*
-                * Increase/decrease loop var
+                * Increase/decrease loop value, unless it would overflow, in which
+                * case exit the loop.
                 */
                if (stmt->reverse)
-                       var->value -= by_value;
+               {
+                       if ((int32) (loop_value - step_value) > loop_value)
+                               break;
+                       loop_value -= step_value;
+               }
                else
-                       var->value += by_value;
+               {
+                       if ((int32) (loop_value + step_value) < loop_value)
+                               break;
+                       loop_value += step_value;
+               }
        }
 
        /*
index c344c9e4ea7ee32f7bb9acb4af842f854ba760cf..4d5eba73e3b43e7c3c96f74e6844cff37d7b1f3c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.59 2007/04/29 01:21:09 neilc Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.60 2007/07/15 02:15:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -701,8 +701,8 @@ dump_fori(PLpgSQL_stmt_fori *stmt)
        dump_expr(stmt->upper);
        printf("\n");
        dump_ind();
-       printf("    by = ");
-       dump_expr(stmt->by);
+       printf("    step = ");
+       dump_expr(stmt->step);
        printf("\n");
        dump_indent -= 2;
 
index 20ee074564a101d23eba4fcbfc269bb8eb1e168e..dce7be2b1bd29fe3f09c71d45812ed6b08e13dca 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.88 2007/04/29 01:21:09 neilc Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.89 2007/07/15 02:15:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -402,7 +402,7 @@ typedef struct
        PLpgSQL_var *var;
        PLpgSQL_expr *lower;
        PLpgSQL_expr *upper;
-       PLpgSQL_expr *by;
+       PLpgSQL_expr *step;                     /* NULL means default (ie, BY 1) */
        int                     reverse;
        List       *body;                       /* List of statements */
 } PLpgSQL_stmt_fori;