Reject zero or negative BY step in plpgsql integer FOR-loops, and behave

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.
This commit is contained in:
Tom Lane 2007-07-15 02:15:04 +00:00
parent a69f9028b5
commit 816ff27f60
4 changed files with 68 additions and 58 deletions

View 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;
expr_by = NULL;
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;
}
/* 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;
}

View 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;
}
}
/*

View 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;

View 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;