Commit 15c72174 authored by Tom Lane's avatar Tom Lane

Apply code-reviewed version of for-scalar-list patch: mostly, fixing

it to report reasonable errors in error cases.
parent 58634caa
<!--
$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.85 2006/02/12 06:03:38 momjian Exp $
$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.86 2006/02/12 06:37:05 tgl Exp $
-->
<chapter id="plpgsql">
......@@ -1968,10 +1968,12 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
</synopsis>
<para>
This form of <literal>FOR</> creates a loop that iterates over a range of integer
values. The variable
This form of <literal>FOR</> creates a loop that iterates over a range
of integer values. The variable
<replaceable>name</replaceable> is automatically defined as type
<type>integer</> and exists only inside the loop. The two expressions giving
<type>integer</> and exists only inside the loop (any existing
definition of the variable name is ignored within the loop).
The two expressions giving
the lower and upper bound of the range are evaluated once when entering
the loop. The iteration step is normally 1, but is -1 when <literal>REVERSE</> is
specified.
......@@ -2012,9 +2014,9 @@ FOR <replaceable>target</replaceable> IN <replaceable>query</replaceable> LOOP
<replaceable>statements</replaceable>
END LOOP <optional> <replaceable>label</replaceable> </optional>;
</synopsis>
<replaceable>Target</replaceable> is a record variable, row variable,
or a comma-separated list of simple variables and record/row fields
which is successively assigned each row
The <replaceable>target</replaceable> is a record variable, row variable,
or comma-separated list of scalar variables.
The <replaceable>target</replaceable> is successively assigned each row
resulting from the <replaceable>query</replaceable> (which must be a
<command>SELECT</command> command) and the loop body is executed for each
row. Here is an example:
......@@ -2069,7 +2071,8 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
<literal>IN</> and <literal>LOOP</>. If <literal>..</> is not seen then
the loop is presumed to be a loop over rows. Mistyping the <literal>..</>
is thus likely to lead to a complaint along the lines of
<quote>loop variable of loop over rows must be a record or row or scalar variable</>,
<quote>loop variable of loop over rows must be a record or row variable
or list of scalar variables</>,
rather than the simple syntax error one might expect to get.
</para>
</note>
......
......@@ -4,7 +4,7 @@
* procedural language
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.84 2006/02/12 06:03:38 momjian Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.85 2006/02/12 06:37:05 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
......@@ -54,13 +54,14 @@ static PLpgSQL_stmt *make_fetch_stmt(void);
static void check_assignable(PLpgSQL_datum *datum);
static PLpgSQL_row *read_into_scalar_list(const char *initial_name,
PLpgSQL_datum *initial_datum);
static PLpgSQL_row *make_scalar_list1(const char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno);
static void check_sql_expr(const char *stmt);
static void plpgsql_sql_error_callback(void *arg);
static void check_labels(const char *start_label,
const char *end_label);
static PLpgSQL_row *make_scalar_list1(const char *name,
PLpgSQL_datum *variable);
%}
%union {
......@@ -76,9 +77,9 @@ static PLpgSQL_row *make_scalar_list1(const char *name,
{
char *name;
int lineno;
PLpgSQL_datum *scalar;
PLpgSQL_rec *rec;
PLpgSQL_row *row;
PLpgSQL_datum *scalar;
} forvariable;
struct
{
......@@ -895,13 +896,14 @@ for_control :
}
else if ($2.scalar)
{
new->row = make_scalar_list1($2.name, $2.scalar);
check_assignable((PLpgSQL_datum *) new->row);
/* convert single scalar to list */
new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
/* no need for check_assignable */
}
else
{
plpgsql_error_lineno = $1;
yyerror("loop variable of loop over rows must be a record, row, or scalar variable");
plpgsql_error_lineno = $2.lineno;
yyerror("loop variable of loop over rows must be a record or row variable or list of scalar variables");
}
new->query = expr;
......@@ -950,24 +952,24 @@ for_control :
PLpgSQL_expr *expr2;
PLpgSQL_var *fvar;
PLpgSQL_stmt_fori *new;
char *varname;
/* First expression is well-formed */
check_sql_expr(expr1->query);
expr2 = plpgsql_read_expression(K_LOOP, "LOOP");
/* T_SCALAR identifier waits for converting */
if ($2.scalar)
{
char *name;
plpgsql_convert_ident($2.name, &name, 1);
pfree($2.name);
$2.name = name;
}
/* should have had a single variable name */
plpgsql_error_lineno = $2.lineno;
if ($2.scalar && $2.row)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("integer FOR loop must have just one target variable")));
/* create loop's private variable */
plpgsql_convert_ident($2.name, &varname, 1);
fvar = (PLpgSQL_var *)
plpgsql_build_variable($2.name,
plpgsql_build_variable(varname,
$2.lineno,
plpgsql_build_datatype(INT4OID,
-1),
......@@ -1021,13 +1023,14 @@ for_control :
}
else if ($2.scalar)
{
new->row = make_scalar_list1($2.name, $2.scalar);
check_assignable((PLpgSQL_datum *) new->row);
/* convert single scalar to list */
new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
/* no need for check_assignable */
}
else
{
plpgsql_error_lineno = $1;
yyerror("loop variable of loop over rows must be record, row, or scalar variable");
plpgsql_error_lineno = $2.lineno;
yyerror("loop variable of loop over rows must be a record or row variable or list of scalar variables");
}
new->query = expr1;
......@@ -1047,55 +1050,63 @@ for_control :
* if any, because that's what we need for the loop-over-query case. Note
* that we must NOT apply check_assignable() or any other semantic check
* until we know what's what.
*
* However, if we see a comma-separated list of names, we know that it
* can't be an integer FOR loop and so it's OK to check the variables
* immediately. In particular, for T_WORD followed by comma, we should
* complain that the name is not known rather than say it's a syntax error.
* Note that the non-error result of this case sets *both* $$.scalar and
* $$.row; see the for_control production.
*/
for_variable : T_SCALAR
{
int tok;
char *name;
name = pstrdup(yytext);
$$.scalar = yylval.scalar;
$$.lineno = plpgsql_scanner_lineno();
{
int tok;
if((tok = yylex()) == ',')
{
plpgsql_push_back_token(tok);
$$.name = NULL;
$$.row = read_into_scalar_list(name, $$.scalar);
$$.rec = NULL;
$$.scalar = NULL;
pfree(name);
}
else
{
plpgsql_push_back_token(tok);
$$.name = name;
$$.row = NULL;
$$.rec = NULL;
}
$$.name = pstrdup(yytext);
$$.lineno = plpgsql_scanner_lineno();
$$.scalar = yylval.scalar;
$$.rec = NULL;
$$.row = NULL;
/* check for comma-separated list */
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == ',')
$$.row = read_into_scalar_list($$.name, $$.scalar);
}
| T_WORD
{
char *name;
int tok;
plpgsql_convert_ident(yytext, &name, 1);
$$.name = name;
$$.name = pstrdup(yytext);
$$.lineno = plpgsql_scanner_lineno();
$$.scalar = NULL;
$$.rec = NULL;
$$.row = NULL;
/* check for comma-separated list */
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == ',')
{
plpgsql_error_lineno = $$.lineno;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
$$.name)));
}
}
| T_RECORD
{
$$.name = NULL;
$$.name = pstrdup(yytext);
$$.lineno = plpgsql_scanner_lineno();
$$.scalar = NULL;
$$.rec = yylval.rec;
$$.row = NULL;
}
| T_ROW
{
$$.name = NULL;
$$.name = pstrdup(yytext);
$$.lineno = plpgsql_scanner_lineno();
$$.scalar = NULL;
$$.row = yylval.row;
$$.rec = NULL;
}
......@@ -2121,30 +2132,6 @@ make_fetch_stmt(void)
}
static PLpgSQL_row *
make_scalar_list1(const char *name,
PLpgSQL_datum *variable)
{
PLpgSQL_row *row;
check_assignable(variable);
row = palloc(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = pstrdup("*internal*");
row->lineno = plpgsql_scanner_lineno();
row->rowtupdesc = NULL;
row->nfields = 1;
row->fieldnames = palloc(sizeof(char *) * 1);
row->varnos = palloc(sizeof(int) * 1);
row->fieldnames[0] = pstrdup(name);
row->varnos[0] = variable->dno;
plpgsql_adddatum((PLpgSQL_datum *)row);
return row;
}
static void
check_assignable(PLpgSQL_datum *datum)
{
......@@ -2256,6 +2243,37 @@ read_into_scalar_list(const char *initial_name,
return row;
}
/*
* Convert a single scalar into a "row" list. This is exactly
* like read_into_scalar_list except we never consume any input.
* In fact, since this can be invoked long after the source
* input was actually read, the lineno has to be passed in.
*/
static PLpgSQL_row *
make_scalar_list1(const char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno)
{
PLpgSQL_row *row;
check_assignable(initial_datum);
row = palloc(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = pstrdup("*internal*");
row->lineno = lineno;
row->rowtupdesc = NULL;
row->nfields = 1;
row->fieldnames = palloc(sizeof(char *));
row->varnos = palloc(sizeof(int));
row->fieldnames[0] = pstrdup(initial_name);
row->varnos[0] = initial_datum->dno;
plpgsql_adddatum((PLpgSQL_datum *)row);
return row;
}
/*
* When the PL/PgSQL parser expects to see a SQL statement, it is very
* liberal in what it accepts; for example, we often assume an
......
......@@ -2722,22 +2722,53 @@ $$ language plpgsql;
ERROR: end label "outer_label" specified for unlabelled block
CONTEXT: compile of PL/pgSQL function "end_label4" near line 5
-- using list of scalars in fori and fore stmts
create function for_vect() returns void as $$
create function for_vect() returns void as $proc$
<<lbl>>declare a integer; b varchar; c varchar; r record;
begin
-- old fori
for i in 1 .. 10 loop
-- fori
for i in 1 .. 3 loop
raise notice '%', i;
end loop;
for a in select 1 from generate_series(1,4) loop
-- fore with record var
for r in select gs as aa, 'BB' as bb, 'CC' as cc from generate_series(1,4) gs loop
raise notice '% % %', r.aa, r.bb, r.cc;
end loop;
-- fore with single scalar
for a in select gs from generate_series(1,4) gs loop
raise notice '%', a;
end loop;
for a,b,c in select generate_series, 'BB','CC' from generate_series(1,4) loop
-- fore with multiple scalars
for a,b,c in select gs, 'BB','CC' from generate_series(1,4) gs loop
raise notice '% % %', a, b, c;
end loop;
-- using qualified names in fors, fore is enabled, disabled only for fori
for lbl.a, lbl.b, lbl.c in execute E'select generate_series, \'bb\',\'cc\' from generate_series(1,4)' loop
for lbl.a, lbl.b, lbl.c in execute $$select gs, 'bb','cc' from generate_series(1,4) gs$$ loop
raise notice '% % %', a, b, c;
end loop;
end;
$$ language plpgsql;
$proc$ language plpgsql;
select for_vect();
NOTICE: 1
NOTICE: 2
NOTICE: 3
NOTICE: 1 BB CC
NOTICE: 2 BB CC
NOTICE: 3 BB CC
NOTICE: 4 BB CC
NOTICE: 1
NOTICE: 2
NOTICE: 3
NOTICE: 4
NOTICE: 1 BB CC
NOTICE: 2 BB CC
NOTICE: 3 BB CC
NOTICE: 4 BB CC
NOTICE: 1 bb cc
NOTICE: 2 bb cc
NOTICE: 3 bb cc
NOTICE: 4 bb cc
for_vect
----------
(1 row)
......@@ -2281,24 +2281,31 @@ begin
end;
$$ language plpgsql;
-- using list of scalars in fori and fore stmts
create function for_vect() returns void as $$
create function for_vect() returns void as $proc$
<<lbl>>declare a integer; b varchar; c varchar; r record;
begin
-- old fori
for i in 1 .. 10 loop
-- fori
for i in 1 .. 3 loop
raise notice '%', i;
end loop;
for a in select 1 from generate_series(1,4) loop
-- fore with record var
for r in select gs as aa, 'BB' as bb, 'CC' as cc from generate_series(1,4) gs loop
raise notice '% % %', r.aa, r.bb, r.cc;
end loop;
-- fore with single scalar
for a in select gs from generate_series(1,4) gs loop
raise notice '%', a;
end loop;
for a,b,c in select generate_series, 'BB','CC' from generate_series(1,4) loop
-- fore with multiple scalars
for a,b,c in select gs, 'BB','CC' from generate_series(1,4) gs loop
raise notice '% % %', a, b, c;
end loop;
-- using qualified names in fors, fore is enabled, disabled only for fori
for lbl.a, lbl.b, lbl.c in execute E'select generate_series, \'bb\',\'cc\' from generate_series(1,4)' loop
for lbl.a, lbl.b, lbl.c in execute $$select gs, 'bb','cc' from generate_series(1,4) gs$$ loop
raise notice '% % %', a, b, c;
end loop;
end;
$$ language plpgsql;
$proc$ language plpgsql;
select for_vect();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment