mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-02-05 19:09:58 +08:00
Apply code-reviewed version of for-scalar-list patch: mostly, fixing
it to report reasonable errors in error cases.
This commit is contained in:
parent
58634caa0f
commit
15c72174f3
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$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();
|
||||
|
Loading…
Reference in New Issue
Block a user