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 * 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); check_labels($1, $4.end_label);
/* close namespace started in opt_label */ /* close namespace started in opt_block_label */
plpgsql_ns_pop(); plpgsql_ns_pop();
} }
; ;
@ -968,39 +968,25 @@ for_control :
PLpgSQL_stmt_fori *new; PLpgSQL_stmt_fori *new;
char *varname; char *varname;
/* First expression is well-formed */ /* Check first expression is well-formed */
check_sql_expr(expr1->query); check_sql_expr(expr1->query);
/* Read and check the second one */
expr2 = read_sql_construct(K_BY, expr2 = read_sql_construct(K_LOOP,
K_LOOP, K_BY,
"LOOP", "LOOP",
"SELECT ", "SELECT ",
true, true,
false, true,
&tok); &tok);
/* Get the BY clause if any */
if (tok == K_BY) if (tok == K_BY)
expr_by = plpgsql_read_expression(K_LOOP, "LOOP"); expr_by = plpgsql_read_expression(K_LOOP, "LOOP");
else else
{ expr_by = NULL;
/*
* If there is no BY clause we will assume 1
*/
char buf[1024];
PLpgSQL_dstring ds;
plpgsql_dstring_init(&ds); /* Should have had a single variable name */
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 */
plpgsql_error_lineno = $2.lineno; plpgsql_error_lineno = $2.lineno;
if ($2.scalar && $2.row) if ($2.scalar && $2.row)
ereport(ERROR, ereport(ERROR,
@ -1023,7 +1009,7 @@ for_control :
new->reverse = reverse; new->reverse = reverse;
new->lower = expr1; new->lower = expr1;
new->upper = expr2; new->upper = expr2;
new->by = expr_by; new->step = expr_by;
$$ = (PLpgSQL_stmt *) new; $$ = (PLpgSQL_stmt *) new;
} }

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * exec_stmt_fori Iterate an integer variable
* from a lower to an upper value * from a lower to an upper value
* incrementing or decrementing in BY value * incrementing or decrementing by the BY value
* Loop can be left with exit.
* ---------- * ----------
*/ */
static int static int
@ -1526,16 +1525,18 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
{ {
PLpgSQL_var *var; PLpgSQL_var *var;
Datum value; Datum value;
Datum by_value;
Oid valtype;
bool isnull; bool isnull;
Oid valtype;
int32 loop_value;
int32 end_value;
int32 step_value;
bool found = false; bool found = false;
int rc = PLPGSQL_RC_OK; int rc = PLPGSQL_RC_OK;
var = (PLpgSQL_var *) (estate->datums[stmt->var->varno]); 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_eval_expr(estate, stmt->lower, &isnull, &valtype);
value = exec_cast_value(value, valtype, var->datatype->typoid, 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, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("lower bound of FOR loop cannot be NULL"))); errmsg("lower bound of FOR loop cannot be NULL")));
var->value = value; loop_value = DatumGetInt32(value);
var->isnull = false;
exec_eval_cleanup(estate); exec_eval_cleanup(estate);
/* /*
@ -1562,22 +1562,32 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("upper bound of FOR loop cannot be NULL"))); errmsg("upper bound of FOR loop cannot be NULL")));
end_value = DatumGetInt32(value);
exec_eval_cleanup(estate); exec_eval_cleanup(estate);
/* /*
* Get the by value * Get the step value
*/ */
by_value = exec_eval_expr(estate, stmt->by, &isnull, &valtype); if (stmt->step)
by_value = exec_cast_value(by_value, valtype, var->datatype->typoid, {
value = exec_eval_expr(estate, stmt->step, &isnull, &valtype);
value = exec_cast_value(value, valtype, var->datatype->typoid,
&(var->datatype->typinput), &(var->datatype->typinput),
var->datatype->typioparam, var->datatype->typioparam,
var->datatype->atttypmod, isnull); var->datatype->atttypmod, isnull);
if (isnull) if (isnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("by value of FOR loop cannot be NULL"))); errmsg("BY value of FOR loop cannot be NULL")));
step_value = DatumGetInt32(value);
exec_eval_cleanup(estate); 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 * Now do the loop
@ -1585,21 +1595,27 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
for (;;) for (;;)
{ {
/* /*
* Check bounds * Check against upper bound
*/ */
if (stmt->reverse) if (stmt->reverse)
{ {
if ((int4) (var->value) < (int4) value) if (loop_value < end_value)
break; break;
} }
else else
{ {
if ((int4) (var->value) > (int4) value) if (loop_value > end_value)
break; break;
} }
found = true; /* looped at least once */ found = true; /* looped at least once */
/*
* Assign current value to loop var
*/
var->value = Int32GetDatum(loop_value);
var->isnull = false;
/* /*
* Execute the statements * 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 * current statement's label, if any: return RC_EXIT so that the
* EXIT continues to propagate up the stack. * EXIT continues to propagate up the stack.
*/ */
break; break;
} }
else if (rc == PLPGSQL_RC_CONTINUE) else if (rc == PLPGSQL_RC_CONTINUE)
{ {
if (estate->exitlabel == NULL) if (estate->exitlabel == NULL)
/* anonymous continue, so re-run the current loop */ /* unlabelled continue, so re-run the current loop */
rc = PLPGSQL_RC_OK; rc = PLPGSQL_RC_OK;
else if (stmt->label != NULL && else if (stmt->label != NULL &&
strcmp(stmt->label, estate->exitlabel) == 0) 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) if (stmt->reverse)
var->value -= by_value; {
if ((int32) (loop_value - step_value) > loop_value)
break;
loop_value -= step_value;
}
else 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 * 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); dump_expr(stmt->upper);
printf("\n"); printf("\n");
dump_ind(); dump_ind();
printf(" by = "); printf(" step = ");
dump_expr(stmt->by); dump_expr(stmt->step);
printf("\n"); printf("\n");
dump_indent -= 2; dump_indent -= 2;

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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_var *var;
PLpgSQL_expr *lower; PLpgSQL_expr *lower;
PLpgSQL_expr *upper; PLpgSQL_expr *upper;
PLpgSQL_expr *by; PLpgSQL_expr *step; /* NULL means default (ie, BY 1) */
int reverse; int reverse;
List *body; /* List of statements */ List *body; /* List of statements */
} PLpgSQL_stmt_fori; } PLpgSQL_stmt_fori;