Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 15c7217

Browse files
committed
Apply code-reviewed version of for-scalar-list patch: mostly, fixing
it to report reasonable errors in error cases.
1 parent 58634ca commit 15c7217

File tree

4 files changed

+157
-98
lines changed

4 files changed

+157
-98
lines changed

doc/src/sgml/plpgsql.sgml

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.85 2006/02/12 06:03:38 momjian Exp $
2+
$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.86 2006/02/12 06:37:05 tgl Exp $
33
-->
44

55
<chapter id="plpgsql">
@@ -1968,10 +1968,12 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
19681968
</synopsis>
19691969

19701970
<para>
1971-
This form of <literal>FOR</> creates a loop that iterates over a range of integer
1972-
values. The variable
1971+
This form of <literal>FOR</> creates a loop that iterates over a range
1972+
of integer values. The variable
19731973
<replaceable>name</replaceable> is automatically defined as type
1974-
<type>integer</> and exists only inside the loop. The two expressions giving
1974+
<type>integer</> and exists only inside the loop (any existing
1975+
definition of the variable name is ignored within the loop).
1976+
The two expressions giving
19751977
the lower and upper bound of the range are evaluated once when entering
19761978
the loop. The iteration step is normally 1, but is -1 when <literal>REVERSE</> is
19771979
specified.
@@ -2012,9 +2014,9 @@ FOR <replaceable>target</replaceable> IN <replaceable>query</replaceable> LOOP
20122014
<replaceable>statements</replaceable>
20132015
END LOOP <optional> <replaceable>label</replaceable> </optional>;
20142016
</synopsis>
2015-
<replaceable>Target</replaceable> is a record variable, row variable,
2016-
or a comma-separated list of simple variables and record/row fields
2017-
which is successively assigned each row
2017+
The <replaceable>target</replaceable> is a record variable, row variable,
2018+
or comma-separated list of scalar variables.
2019+
The <replaceable>target</replaceable> is successively assigned each row
20182020
resulting from the <replaceable>query</replaceable> (which must be a
20192021
<command>SELECT</command> command) and the loop body is executed for each
20202022
row. Here is an example:
@@ -2069,7 +2071,8 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
20692071
<literal>IN</> and <literal>LOOP</>. If <literal>..</> is not seen then
20702072
the loop is presumed to be a loop over rows. Mistyping the <literal>..</>
20712073
is thus likely to lead to a complaint along the lines of
2072-
<quote>loop variable of loop over rows must be a record or row or scalar variable</>,
2074+
<quote>loop variable of loop over rows must be a record or row variable
2075+
or list of scalar variables</>,
20732076
rather than the simple syntax error one might expect to get.
20742077
</para>
20752078
</note>

src/pl/plpgsql/src/gram.y

+93-75
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* procedural language
55
*
66
* IDENTIFICATION
7-
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.84 2006/02/12 06:03:38 momjian Exp $
7+
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.85 2006/02/12 06:37:05 tgl Exp $
88
*
99
* This software is copyrighted by Jan Wieck - Hamburg.
1010
*
@@ -54,13 +54,14 @@ static PLpgSQL_stmt *make_fetch_stmt(void);
5454
static void check_assignable(PLpgSQL_datum *datum);
5555
static PLpgSQL_row *read_into_scalar_list(const char *initial_name,
5656
PLpgSQL_datum *initial_datum);
57+
static PLpgSQL_row *make_scalar_list1(const char *initial_name,
58+
PLpgSQL_datum *initial_datum,
59+
int lineno);
5760
static void check_sql_expr(const char *stmt);
5861
static void plpgsql_sql_error_callback(void *arg);
5962
static void check_labels(const char *start_label,
6063
const char *end_label);
61-
static PLpgSQL_row *make_scalar_list1(const char *name,
62-
PLpgSQL_datum *variable);
63-
64+
6465
%}
6566

6667
%union {
@@ -76,9 +77,9 @@ static PLpgSQL_row *make_scalar_list1(const char *name,
7677
{
7778
char *name;
7879
int lineno;
80+
PLpgSQL_datum *scalar;
7981
PLpgSQL_rec *rec;
8082
PLpgSQL_row *row;
81-
PLpgSQL_datum *scalar;
8283
} forvariable;
8384
struct
8485
{
@@ -895,13 +896,14 @@ for_control :
895896
}
896897
else if ($2.scalar)
897898
{
898-
new->row = make_scalar_list1($2.name, $2.scalar);
899-
check_assignable((PLpgSQL_datum *) new->row);
899+
/* convert single scalar to list */
900+
new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
901+
/* no need for check_assignable */
900902
}
901903
else
902904
{
903-
plpgsql_error_lineno = $1;
904-
yyerror("loop variable of loop over rows must be a record, row, or scalar variable");
905+
plpgsql_error_lineno = $2.lineno;
906+
yyerror("loop variable of loop over rows must be a record or row variable or list of scalar variables");
905907
}
906908
new->query = expr;
907909

@@ -950,24 +952,24 @@ for_control :
950952
PLpgSQL_expr *expr2;
951953
PLpgSQL_var *fvar;
952954
PLpgSQL_stmt_fori *new;
955+
char *varname;
953956

954957
/* First expression is well-formed */
955958
check_sql_expr(expr1->query);
956959

957960
expr2 = plpgsql_read_expression(K_LOOP, "LOOP");
958961

959-
/* T_SCALAR identifier waits for converting */
960-
if ($2.scalar)
961-
{
962-
char *name;
963-
plpgsql_convert_ident($2.name, &name, 1);
964-
pfree($2.name);
965-
$2.name = name;
966-
}
962+
/* should have had a single variable name */
963+
plpgsql_error_lineno = $2.lineno;
964+
if ($2.scalar && $2.row)
965+
ereport(ERROR,
966+
(errcode(ERRCODE_SYNTAX_ERROR),
967+
errmsg("integer FOR loop must have just one target variable")));
967968

968969
/* create loop's private variable */
970+
plpgsql_convert_ident($2.name, &varname, 1);
969971
fvar = (PLpgSQL_var *)
970-
plpgsql_build_variable($2.name,
972+
plpgsql_build_variable(varname,
971973
$2.lineno,
972974
plpgsql_build_datatype(INT4OID,
973975
-1),
@@ -1021,13 +1023,14 @@ for_control :
10211023
}
10221024
else if ($2.scalar)
10231025
{
1024-
new->row = make_scalar_list1($2.name, $2.scalar);
1025-
check_assignable((PLpgSQL_datum *) new->row);
1026+
/* convert single scalar to list */
1027+
new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
1028+
/* no need for check_assignable */
10261029
}
10271030
else
10281031
{
1029-
plpgsql_error_lineno = $1;
1030-
yyerror("loop variable of loop over rows must be record, row, or scalar variable");
1032+
plpgsql_error_lineno = $2.lineno;
1033+
yyerror("loop variable of loop over rows must be a record or row variable or list of scalar variables");
10311034
}
10321035

10331036
new->query = expr1;
@@ -1047,55 +1050,63 @@ for_control :
10471050
* if any, because that's what we need for the loop-over-query case. Note
10481051
* that we must NOT apply check_assignable() or any other semantic check
10491052
* until we know what's what.
1053+
*
1054+
* However, if we see a comma-separated list of names, we know that it
1055+
* can't be an integer FOR loop and so it's OK to check the variables
1056+
* immediately. In particular, for T_WORD followed by comma, we should
1057+
* complain that the name is not known rather than say it's a syntax error.
1058+
* Note that the non-error result of this case sets *both* $$.scalar and
1059+
* $$.row; see the for_control production.
10501060
*/
10511061
for_variable : T_SCALAR
1052-
{
1053-
int tok;
1054-
char *name;
1055-
1056-
name = pstrdup(yytext);
1057-
$$.scalar = yylval.scalar;
1058-
$$.lineno = plpgsql_scanner_lineno();
1062+
{
1063+
int tok;
10591064

1060-
if((tok = yylex()) == ',')
1061-
{
1062-
plpgsql_push_back_token(tok);
1063-
$$.name = NULL;
1064-
$$.row = read_into_scalar_list(name, $$.scalar);
1065-
$$.rec = NULL;
1066-
$$.scalar = NULL;
1067-
1068-
pfree(name);
1069-
}
1070-
else
1071-
{
1072-
plpgsql_push_back_token(tok);
1073-
$$.name = name;
1074-
$$.row = NULL;
1075-
$$.rec = NULL;
1076-
}
1065+
$$.name = pstrdup(yytext);
1066+
$$.lineno = plpgsql_scanner_lineno();
1067+
$$.scalar = yylval.scalar;
1068+
$$.rec = NULL;
1069+
$$.row = NULL;
1070+
/* check for comma-separated list */
1071+
tok = yylex();
1072+
plpgsql_push_back_token(tok);
1073+
if (tok == ',')
1074+
$$.row = read_into_scalar_list($$.name, $$.scalar);
10771075
}
10781076
| T_WORD
10791077
{
1080-
char *name;
1078+
int tok;
10811079

1082-
plpgsql_convert_ident(yytext, &name, 1);
1083-
$$.name = name;
1080+
$$.name = pstrdup(yytext);
10841081
$$.lineno = plpgsql_scanner_lineno();
1082+
$$.scalar = NULL;
10851083
$$.rec = NULL;
10861084
$$.row = NULL;
1085+
/* check for comma-separated list */
1086+
tok = yylex();
1087+
plpgsql_push_back_token(tok);
1088+
if (tok == ',')
1089+
{
1090+
plpgsql_error_lineno = $$.lineno;
1091+
ereport(ERROR,
1092+
(errcode(ERRCODE_SYNTAX_ERROR),
1093+
errmsg("\"%s\" is not a scalar variable",
1094+
$$.name)));
1095+
}
10871096
}
10881097
| T_RECORD
10891098
{
1090-
$$.name = NULL;
1099+
$$.name = pstrdup(yytext);
10911100
$$.lineno = plpgsql_scanner_lineno();
1101+
$$.scalar = NULL;
10921102
$$.rec = yylval.rec;
10931103
$$.row = NULL;
10941104
}
10951105
| T_ROW
10961106
{
1097-
$$.name = NULL;
1107+
$$.name = pstrdup(yytext);
10981108
$$.lineno = plpgsql_scanner_lineno();
1109+
$$.scalar = NULL;
10991110
$$.row = yylval.row;
11001111
$$.rec = NULL;
11011112
}
@@ -2121,30 +2132,6 @@ make_fetch_stmt(void)
21212132
}
21222133

21232134

2124-
static PLpgSQL_row *
2125-
make_scalar_list1(const char *name,
2126-
PLpgSQL_datum *variable)
2127-
{
2128-
PLpgSQL_row *row;
2129-
check_assignable(variable);
2130-
2131-
row = palloc(sizeof(PLpgSQL_row));
2132-
row->dtype = PLPGSQL_DTYPE_ROW;
2133-
row->refname = pstrdup("*internal*");
2134-
row->lineno = plpgsql_scanner_lineno();
2135-
row->rowtupdesc = NULL;
2136-
row->nfields = 1;
2137-
row->fieldnames = palloc(sizeof(char *) * 1);
2138-
row->varnos = palloc(sizeof(int) * 1);
2139-
row->fieldnames[0] = pstrdup(name);
2140-
row->varnos[0] = variable->dno;
2141-
2142-
plpgsql_adddatum((PLpgSQL_datum *)row);
2143-
2144-
return row;
2145-
}
2146-
2147-
21482135
static void
21492136
check_assignable(PLpgSQL_datum *datum)
21502137
{
@@ -2256,6 +2243,37 @@ read_into_scalar_list(const char *initial_name,
22562243
return row;
22572244
}
22582245

2246+
/*
2247+
* Convert a single scalar into a "row" list. This is exactly
2248+
* like read_into_scalar_list except we never consume any input.
2249+
* In fact, since this can be invoked long after the source
2250+
* input was actually read, the lineno has to be passed in.
2251+
*/
2252+
static PLpgSQL_row *
2253+
make_scalar_list1(const char *initial_name,
2254+
PLpgSQL_datum *initial_datum,
2255+
int lineno)
2256+
{
2257+
PLpgSQL_row *row;
2258+
2259+
check_assignable(initial_datum);
2260+
2261+
row = palloc(sizeof(PLpgSQL_row));
2262+
row->dtype = PLPGSQL_DTYPE_ROW;
2263+
row->refname = pstrdup("*internal*");
2264+
row->lineno = lineno;
2265+
row->rowtupdesc = NULL;
2266+
row->nfields = 1;
2267+
row->fieldnames = palloc(sizeof(char *));
2268+
row->varnos = palloc(sizeof(int));
2269+
row->fieldnames[0] = pstrdup(initial_name);
2270+
row->varnos[0] = initial_datum->dno;
2271+
2272+
plpgsql_adddatum((PLpgSQL_datum *)row);
2273+
2274+
return row;
2275+
}
2276+
22592277
/*
22602278
* When the PL/PgSQL parser expects to see a SQL statement, it is very
22612279
* liberal in what it accepts; for example, we often assume an

src/test/regress/expected/plpgsql.out

+38-7
Original file line numberDiff line numberDiff line change
@@ -2722,22 +2722,53 @@ $$ language plpgsql;
27222722
ERROR: end label "outer_label" specified for unlabelled block
27232723
CONTEXT: compile of PL/pgSQL function "end_label4" near line 5
27242724
-- using list of scalars in fori and fore stmts
2725-
create function for_vect() returns void as $$
2725+
create function for_vect() returns void as $proc$
27262726
<<lbl>>declare a integer; b varchar; c varchar; r record;
27272727
begin
2728-
-- old fori
2729-
for i in 1 .. 10 loop
2728+
-- fori
2729+
for i in 1 .. 3 loop
27302730
raise notice '%', i;
27312731
end loop;
2732-
for a in select 1 from generate_series(1,4) loop
2732+
-- fore with record var
2733+
for r in select gs as aa, 'BB' as bb, 'CC' as cc from generate_series(1,4) gs loop
2734+
raise notice '% % %', r.aa, r.bb, r.cc;
2735+
end loop;
2736+
-- fore with single scalar
2737+
for a in select gs from generate_series(1,4) gs loop
27332738
raise notice '%', a;
27342739
end loop;
2735-
for a,b,c in select generate_series, 'BB','CC' from generate_series(1,4) loop
2740+
-- fore with multiple scalars
2741+
for a,b,c in select gs, 'BB','CC' from generate_series(1,4) gs loop
27362742
raise notice '% % %', a, b, c;
27372743
end loop;
27382744
-- using qualified names in fors, fore is enabled, disabled only for fori
2739-
for lbl.a, lbl.b, lbl.c in execute E'select generate_series, \'bb\',\'cc\' from generate_series(1,4)' loop
2745+
for lbl.a, lbl.b, lbl.c in execute $$select gs, 'bb','cc' from generate_series(1,4) gs$$ loop
27402746
raise notice '% % %', a, b, c;
27412747
end loop;
27422748
end;
2743-
$$ language plpgsql;
2749+
$proc$ language plpgsql;
2750+
select for_vect();
2751+
NOTICE: 1
2752+
NOTICE: 2
2753+
NOTICE: 3
2754+
NOTICE: 1 BB CC
2755+
NOTICE: 2 BB CC
2756+
NOTICE: 3 BB CC
2757+
NOTICE: 4 BB CC
2758+
NOTICE: 1
2759+
NOTICE: 2
2760+
NOTICE: 3
2761+
NOTICE: 4
2762+
NOTICE: 1 BB CC
2763+
NOTICE: 2 BB CC
2764+
NOTICE: 3 BB CC
2765+
NOTICE: 4 BB CC
2766+
NOTICE: 1 bb cc
2767+
NOTICE: 2 bb cc
2768+
NOTICE: 3 bb cc
2769+
NOTICE: 4 bb cc
2770+
for_vect
2771+
----------
2772+
2773+
(1 row)
2774+

0 commit comments

Comments
 (0)