Commit a72dd7a9 authored by Tom Lane's avatar Tom Lane

Okay, I've had it with answering newbie questions about why plpgsql

FOR loops are giving weird syntax errors.  Restructure parsing of FOR
loops so that the integer-loop-vs-query-loop decision is driven off
the presence of '..' between IN and LOOP, rather than the presence
of a matching record/row variable name.  Hopefully this will make the
behavior a bit more transparent.
parent f5c798ee
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.38 2004/05/16 23:22:06 neilc Exp $ $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.39 2004/07/04 02:48:52 tgl Exp $
--> -->
<chapter id="plpgsql"> <chapter id="plpgsql">
...@@ -1769,7 +1769,7 @@ END; ...@@ -1769,7 +1769,7 @@ END;
<para> <para>
The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over
records: rows:
<synopsis> <synopsis>
<optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional> <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
FOR <replaceable>record_or_row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP FOR <replaceable>record_or_row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP
...@@ -1788,13 +1788,12 @@ END LOOP; ...@@ -1788,13 +1788,12 @@ END LOOP;
<para> <para>
The <application>PL/pgSQL</> parser presently distinguishes the The <application>PL/pgSQL</> parser presently distinguishes the
two kinds of <literal>FOR</> loops (integer or query result) by checking two kinds of <literal>FOR</> loops (integer or query result) by checking
whether the target variable mentioned just after <literal>FOR</> has been whether <literal>..</> appears outside any parentheses between
declared as a record or row variable. If not, it's presumed to be <literal>IN</> and <literal>LOOP</>. If <literal>..</> is not seen then
an integer <literal>FOR</> loop. This can cause rather nonintuitive error the loop is presumed to be a loop over rows. Mistyping the <literal>..</>
messages when the true problem is, say, that one has is thus likely to lead to a complaint along the lines of
misspelled the variable name after the <literal>FOR</>. Typically <quote>loop variable of loop over rows must be a record or row</>,
the complaint will be something like <literal>missing ".." at end of SQL rather than the simple syntax error one might expect to get.
expression</>.
</para> </para>
</note> </note>
</sect2> </sect2>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.56 2004/06/04 02:37:06 tgl Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.57 2004/07/04 02:49:04 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -40,9 +40,11 @@ ...@@ -40,9 +40,11 @@
static PLpgSQL_expr *read_sql_construct(int until, static PLpgSQL_expr *read_sql_construct(int until,
int until2,
const char *expected, const char *expected,
bool isexpression, bool isexpression,
const char *sqlstart); const char *sqlstart,
int *endtoken);
static PLpgSQL_expr *read_sql_stmt(const char *sqlstart); static PLpgSQL_expr *read_sql_stmt(const char *sqlstart);
static PLpgSQL_type *read_datatype(int tok); static PLpgSQL_type *read_datatype(int tok);
static PLpgSQL_stmt *make_select_stmt(void); static PLpgSQL_stmt *make_select_stmt(void);
...@@ -73,9 +75,11 @@ static void check_assignable(PLpgSQL_datum *datum); ...@@ -73,9 +75,11 @@ static void check_assignable(PLpgSQL_datum *datum);
} dtlist; } dtlist;
struct struct
{ {
int reverse; char *name;
PLpgSQL_expr *expr; int lineno;
} forilow; PLpgSQL_rec *rec;
PLpgSQL_row *row;
} forvariable;
struct struct
{ {
char *label; char *label;
...@@ -110,11 +114,10 @@ static void check_assignable(PLpgSQL_datum *datum); ...@@ -110,11 +114,10 @@ static void check_assignable(PLpgSQL_datum *datum);
%type <expr> opt_exitcond %type <expr> opt_exitcond
%type <ival> assign_var cursor_variable %type <ival> assign_var cursor_variable
%type <var> fori_var cursor_varptr %type <var> cursor_varptr
%type <variable> decl_cursor_arg %type <variable> decl_cursor_arg
%type <varname> fori_varname %type <forvariable> for_variable
%type <forilow> fori_lower %type <stmt> for_control
%type <rec> fors_target
%type <str> opt_lblname opt_label %type <str> opt_lblname opt_label
%type <str> opt_exitlabel %type <str> opt_exitlabel
...@@ -124,8 +127,8 @@ static void check_assignable(PLpgSQL_datum *datum); ...@@ -124,8 +127,8 @@ static void check_assignable(PLpgSQL_datum *datum);
%type <stmt> proc_stmt pl_block %type <stmt> proc_stmt pl_block
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit %type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
%type <stmt> stmt_return stmt_return_next stmt_raise stmt_execsql %type <stmt> stmt_return stmt_return_next stmt_raise stmt_execsql
%type <stmt> stmt_fori stmt_fors stmt_select stmt_perform %type <stmt> stmt_for stmt_select stmt_perform
%type <stmt> stmt_dynexecute stmt_dynfors stmt_getdiag %type <stmt> stmt_dynexecute stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_close %type <stmt> stmt_open stmt_fetch stmt_close
%type <intlist> raise_params %type <intlist> raise_params
...@@ -616,9 +619,7 @@ proc_stmt : pl_block ';' ...@@ -616,9 +619,7 @@ proc_stmt : pl_block ';'
{ $$ = $1; } { $$ = $1; }
| stmt_while | stmt_while
{ $$ = $1; } { $$ = $1; }
| stmt_fori | stmt_for
{ $$ = $1; }
| stmt_fors
{ $$ = $1; } { $$ = $1; }
| stmt_select | stmt_select
{ $$ = $1; } { $$ = $1; }
...@@ -634,8 +635,6 @@ proc_stmt : pl_block ';' ...@@ -634,8 +635,6 @@ proc_stmt : pl_block ';'
{ $$ = $1; } { $$ = $1; }
| stmt_dynexecute | stmt_dynexecute
{ $$ = $1; } { $$ = $1; }
| stmt_dynfors
{ $$ = $1; }
| stmt_perform | stmt_perform
{ $$ = $1; } { $$ = $1; }
| stmt_getdiag | stmt_getdiag
...@@ -882,152 +881,218 @@ stmt_while : opt_label K_WHILE lno expr_until_loop loop_body ...@@ -882,152 +881,218 @@ stmt_while : opt_label K_WHILE lno expr_until_loop loop_body
} }
; ;
stmt_fori : opt_label K_FOR lno fori_var K_IN fori_lower expr_until_loop loop_body stmt_for : opt_label K_FOR for_control loop_body
{ {
PLpgSQL_stmt_fori *new; /* This runs after we've scanned the loop body */
if ($3->cmd_type == PLPGSQL_STMT_FORI)
{
PLpgSQL_stmt_fori *new;
new = malloc(sizeof(PLpgSQL_stmt_fori)); new = (PLpgSQL_stmt_fori *) $3;
memset(new, 0, sizeof(PLpgSQL_stmt_fori)); new->label = $1;
new->body = $4;
$$ = (PLpgSQL_stmt *) new;
}
else if ($3->cmd_type == PLPGSQL_STMT_FORS)
{
PLpgSQL_stmt_fors *new;
new->cmd_type = PLPGSQL_STMT_FORI; new = (PLpgSQL_stmt_fors *) $3;
new->lineno = $3; new->label = $1;
new->label = $1; new->body = $4;
new->var = $4; $$ = (PLpgSQL_stmt *) new;
new->reverse = $6.reverse; }
new->lower = $6.expr; else
new->upper = $7; {
new->body = $8; PLpgSQL_stmt_dynfors *new;
plpgsql_ns_pop(); Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
new = (PLpgSQL_stmt_dynfors *) $3;
new->label = $1;
new->body = $4;
$$ = (PLpgSQL_stmt *) new;
}
$$ = (PLpgSQL_stmt *)new; /* close namespace started in opt_label */
plpgsql_ns_pop();
} }
; ;
fori_var : fori_varname for_control : lno for_variable K_IN
{ {
PLpgSQL_var *new; int tok;
bool reverse = false;
bool execute = false;
PLpgSQL_expr *expr1;
new = (PLpgSQL_var *) /* check for REVERSE and EXECUTE */
plpgsql_build_variable($1.name, $1.lineno, tok = yylex();
plpgsql_build_datatype(INT4OID, if (tok == K_REVERSE)
-1), {
true); reverse = true;
tok = yylex();
}
plpgsql_add_initdatums(NULL); if (tok == K_EXECUTE)
execute = true;
else
plpgsql_push_back_token(tok);
$$ = new; /* Collect one or two expressions */
} expr1 = read_sql_construct(K_DOTDOT,
; K_LOOP,
"LOOP",
true,
"SELECT ",
&tok);
fori_varname : T_SCALAR if (tok == K_DOTDOT)
{ {
char *name; /* Found .., so it must be an integer loop */
PLpgSQL_stmt_fori *new;
PLpgSQL_expr *expr2;
PLpgSQL_var *fvar;
plpgsql_convert_ident(yytext, &name, 1); expr2 = plpgsql_read_expression(K_LOOP, "LOOP");
/* name should be malloc'd for use as varname */
$$.name = strdup(name);
$$.lineno = plpgsql_scanner_lineno();
pfree(name);
}
| T_WORD
{
char *name;
plpgsql_convert_ident(yytext, &name, 1); if (execute)
/* name should be malloc'd for use as varname */ {
$$.name = strdup(name); plpgsql_error_lineno = $1;
$$.lineno = plpgsql_scanner_lineno(); yyerror("cannot specify EXECUTE in integer for-loop");
pfree(name); }
}
;
fori_lower : /* name should be malloc'd for use as varname */
{ fvar = (PLpgSQL_var *)
int tok; plpgsql_build_variable(strdup($2.name),
$2.lineno,
plpgsql_build_datatype(INT4OID,
-1),
true);
tok = yylex(); /* put the for-variable into the local block */
if (tok == K_REVERSE) plpgsql_add_initdatums(NULL);
{
$$.reverse = 1; new = malloc(sizeof(PLpgSQL_stmt_fori));
memset(new, 0, sizeof(PLpgSQL_stmt_fori));
new->cmd_type = PLPGSQL_STMT_FORI;
new->lineno = $1;
new->var = fvar;
new->reverse = reverse;
new->lower = expr1;
new->upper = expr2;
$$ = (PLpgSQL_stmt *) new;
} }
else else if (execute)
{ {
$$.reverse = 0; /* No .., so it must be a loop over rows */
plpgsql_push_back_token(tok); PLpgSQL_stmt_dynfors *new;
}
$$.expr = plpgsql_read_expression(K_DOTDOT, ".."); if (reverse)
} {
; plpgsql_error_lineno = $1;
yyerror("cannot specify REVERSE in loop over rows");
}
stmt_fors : opt_label K_FOR lno fors_target K_IN K_SELECT expr_until_loop loop_body new = malloc(sizeof(PLpgSQL_stmt_dynfors));
{ memset(new, 0, sizeof(PLpgSQL_stmt_dynfors));
PLpgSQL_stmt_fors *new;
new = malloc(sizeof(PLpgSQL_stmt_fors)); new->cmd_type = PLPGSQL_STMT_DYNFORS;
memset(new, 0, sizeof(PLpgSQL_stmt_fors)); new->lineno = $1;
if ($2.rec)
new->rec = $2.rec;
else if ($2.row)
new->row = $2.row;
else
{
plpgsql_error_lineno = $1;
yyerror("loop variable of loop over rows must be a record or row variable");
}
new->query = expr1;
new->cmd_type = PLPGSQL_STMT_FORS; $$ = (PLpgSQL_stmt *) new;
new->lineno = $3;
new->label = $1;
switch ($4->dtype)
{
case PLPGSQL_DTYPE_REC:
new->rec = $4;
break;
case PLPGSQL_DTYPE_ROW:
new->row = (PLpgSQL_row *)$4;
break;
default:
elog(ERROR, "unrecognized dtype: %d",
$4->dtype);
} }
new->query = $7; else
new->body = $8; {
/* No .., so it must be a loop over rows */
PLpgSQL_stmt_fors *new;
char *newquery;
plpgsql_ns_pop(); if (reverse)
{
plpgsql_error_lineno = $1;
yyerror("cannot specify REVERSE in loop over rows");
}
$$ = (PLpgSQL_stmt *)new; new = malloc(sizeof(PLpgSQL_stmt_fors));
} memset(new, 0, sizeof(PLpgSQL_stmt_fors));
;
stmt_dynfors : opt_label K_FOR lno fors_target K_IN K_EXECUTE expr_until_loop loop_body new->cmd_type = PLPGSQL_STMT_FORS;
{ new->lineno = $1;
PLpgSQL_stmt_dynfors *new; if ($2.rec)
new->rec = $2.rec;
else if ($2.row)
new->row = $2.row;
else
{
plpgsql_error_lineno = $1;
yyerror("loop variable of loop over rows must be a record or row variable");
}
/*
* Must get rid of the "SELECT " we prepended
* to expr1's text
*/
newquery = strdup(expr1->query + 7);
free(expr1->query);
expr1->query = newquery;
new = malloc(sizeof(PLpgSQL_stmt_dynfors)); new->query = expr1;
memset(new, 0, sizeof(PLpgSQL_stmt_dynfors));
new->cmd_type = PLPGSQL_STMT_DYNFORS; $$ = (PLpgSQL_stmt *) new;
new->lineno = $3;
new->label = $1;
switch ($4->dtype)
{
case PLPGSQL_DTYPE_REC:
new->rec = $4;
break;
case PLPGSQL_DTYPE_ROW:
new->row = (PLpgSQL_row *)$4;
break;
default:
elog(ERROR, "unrecognized dtype: %d",
$4->dtype);
} }
new->query = $7; }
new->body = $8; ;
plpgsql_ns_pop(); for_variable : T_SCALAR
{
char *name;
$$ = (PLpgSQL_stmt *)new; plpgsql_convert_ident(yytext, &name, 1);
$$.name = name;
$$.lineno = plpgsql_scanner_lineno();
$$.rec = NULL;
$$.row = NULL;
} }
; | T_WORD
{
char *name;
plpgsql_convert_ident(yytext, &name, 1);
$$.name = name;
$$.lineno = plpgsql_scanner_lineno();
$$.rec = NULL;
$$.row = NULL;
}
| T_RECORD
{
char *name;
fors_target : T_RECORD plpgsql_convert_ident(yytext, &name, 1);
{ $$ = yylval.rec; } $$.name = name;
$$.lineno = plpgsql_scanner_lineno();
$$.rec = yylval.rec;
$$.row = NULL;
}
| T_ROW | T_ROW
{ {
$$ = (PLpgSQL_rec *)(yylval.row); char *name;
plpgsql_convert_ident(yytext, &name, 1);
$$.name = name;
$$.lineno = plpgsql_scanner_lineno();
$$.row = yylval.row;
$$.rec = NULL;
} }
; ;
...@@ -1521,20 +1586,33 @@ lno : ...@@ -1521,20 +1586,33 @@ lno :
PLpgSQL_expr * PLpgSQL_expr *
plpgsql_read_expression(int until, const char *expected) plpgsql_read_expression(int until, const char *expected)
{ {
return read_sql_construct(until, expected, true, "SELECT "); return read_sql_construct(until, 0, expected, true, "SELECT ", NULL);
} }
static PLpgSQL_expr * static PLpgSQL_expr *
read_sql_stmt(const char *sqlstart) read_sql_stmt(const char *sqlstart)
{ {
return read_sql_construct(';', ";", false, sqlstart); return read_sql_construct(';', 0, ";", false, sqlstart, NULL);
} }
/*
* Read a SQL construct and build a PLpgSQL_expr for it.
*
* until: token code for expected terminator
* until2: token code for alternate terminator (pass 0 if none)
* expected: text to use in complaining that terminator was not found
* isexpression: whether to say we're reading an "expression" or a "statement"
* sqlstart: text to prefix to the accumulated SQL text
* endtoken: if not NULL, ending token is stored at *endtoken
* (this is only interesting if until2 isn't zero)
*/
static PLpgSQL_expr * static PLpgSQL_expr *
read_sql_construct(int until, read_sql_construct(int until,
int until2,
const char *expected, const char *expected,
bool isexpression, bool isexpression,
const char *sqlstart) const char *sqlstart,
int *endtoken)
{ {
int tok; int tok;
int lno; int lno;
...@@ -1554,6 +1632,8 @@ read_sql_construct(int until, ...@@ -1554,6 +1632,8 @@ read_sql_construct(int until,
tok = yylex(); tok = yylex();
if (tok == until && parenlevel == 0) if (tok == until && parenlevel == 0)
break; break;
if (tok == until2 && parenlevel == 0)
break;
if (tok == '(' || tok == '[') if (tok == '(' || tok == '[')
parenlevel++; parenlevel++;
else if (tok == ')' || tok == ']') else if (tok == ')' || tok == ']')
...@@ -1586,7 +1666,6 @@ read_sql_construct(int until, ...@@ -1586,7 +1666,6 @@ read_sql_construct(int until,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("missing \"%s\" at end of SQL statement", errmsg("missing \"%s\" at end of SQL statement",
expected))); expected)));
break;
} }
if (plpgsql_SpaceScanned) if (plpgsql_SpaceScanned)
plpgsql_dstring_append(&ds, " "); plpgsql_dstring_append(&ds, " ");
...@@ -1616,6 +1695,9 @@ read_sql_construct(int until, ...@@ -1616,6 +1695,9 @@ read_sql_construct(int until,
} }
} }
if (endtoken)
*endtoken = tok;
expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int)); expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
expr->dtype = PLPGSQL_DTYPE_EXPR; expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = strdup(plpgsql_dstring_get(&ds)); expr->query = strdup(plpgsql_dstring_get(&ds));
......
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