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.
This commit is contained in:
Tom Lane 2004-07-04 02:49:04 +00:00
parent f5c798ee82
commit a72dd7a9e4
2 changed files with 237 additions and 156 deletions

View file

@ -1,5 +1,5 @@
<!--
$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">
@ -1769,7 +1769,7 @@ END;
<para>
The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over
records:
rows:
<synopsis>
<optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
FOR <replaceable>record_or_row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP
@ -1788,13 +1788,12 @@ END LOOP;
<para>
The <application>PL/pgSQL</> parser presently distinguishes the
two kinds of <literal>FOR</> loops (integer or query result) by checking
whether the target variable mentioned just after <literal>FOR</> has been
declared as a record or row variable. If not, it's presumed to be
an integer <literal>FOR</> loop. This can cause rather nonintuitive error
messages when the true problem is, say, that one has
misspelled the variable name after the <literal>FOR</>. Typically
the complaint will be something like <literal>missing ".." at end of SQL
expression</>.
whether <literal>..</> appears outside any parentheses between
<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</>,
rather than the simple syntax error one might expect to get.
</para>
</note>
</sect2>

View file

@ -4,7 +4,7 @@
* procedural language
*
* 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.
*
@ -40,9 +40,11 @@
static PLpgSQL_expr *read_sql_construct(int until,
int until2,
const char *expected,
bool isexpression,
const char *sqlstart);
const char *sqlstart,
int *endtoken);
static PLpgSQL_expr *read_sql_stmt(const char *sqlstart);
static PLpgSQL_type *read_datatype(int tok);
static PLpgSQL_stmt *make_select_stmt(void);
@ -73,9 +75,11 @@ static void check_assignable(PLpgSQL_datum *datum);
} dtlist;
struct
{
int reverse;
PLpgSQL_expr *expr;
} forilow;
char *name;
int lineno;
PLpgSQL_rec *rec;
PLpgSQL_row *row;
} forvariable;
struct
{
char *label;
@ -110,11 +114,10 @@ static void check_assignable(PLpgSQL_datum *datum);
%type <expr> opt_exitcond
%type <ival> assign_var cursor_variable
%type <var> fori_var cursor_varptr
%type <var> cursor_varptr
%type <variable> decl_cursor_arg
%type <varname> fori_varname
%type <forilow> fori_lower
%type <rec> fors_target
%type <forvariable> for_variable
%type <stmt> for_control
%type <str> opt_lblname opt_label
%type <str> opt_exitlabel
@ -124,8 +127,8 @@ static void check_assignable(PLpgSQL_datum *datum);
%type <stmt> proc_stmt pl_block
%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_fori stmt_fors stmt_select stmt_perform
%type <stmt> stmt_dynexecute stmt_dynfors stmt_getdiag
%type <stmt> stmt_for stmt_select stmt_perform
%type <stmt> stmt_dynexecute stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_close
%type <intlist> raise_params
@ -616,9 +619,7 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_while
{ $$ = $1; }
| stmt_fori
{ $$ = $1; }
| stmt_fors
| stmt_for
{ $$ = $1; }
| stmt_select
{ $$ = $1; }
@ -634,8 +635,6 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_dynexecute
{ $$ = $1; }
| stmt_dynfors
{ $$ = $1; }
| stmt_perform
{ $$ = $1; }
| stmt_getdiag
@ -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;
new = malloc(sizeof(PLpgSQL_stmt_fori));
memset(new, 0, sizeof(PLpgSQL_stmt_fori));
new->cmd_type = PLPGSQL_STMT_FORI;
new->lineno = $3;
new->label = $1;
new->var = $4;
new->reverse = $6.reverse;
new->lower = $6.expr;
new->upper = $7;
new->body = $8;
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)new;
}
;
fori_var : fori_varname
{
PLpgSQL_var *new;
new = (PLpgSQL_var *)
plpgsql_build_variable($1.name, $1.lineno,
plpgsql_build_datatype(INT4OID,
-1),
true);
plpgsql_add_initdatums(NULL);
$$ = new;
}
;
fori_varname : T_SCALAR
{
char *name;
plpgsql_convert_ident(yytext, &name, 1);
/* 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);
/* name should be malloc'd for use as varname */
$$.name = strdup(name);
$$.lineno = plpgsql_scanner_lineno();
pfree(name);
}
;
fori_lower :
{
int tok;
tok = yylex();
if (tok == K_REVERSE)
/* This runs after we've scanned the loop body */
if ($3->cmd_type == PLPGSQL_STMT_FORI)
{
$$.reverse = 1;
PLpgSQL_stmt_fori *new;
new = (PLpgSQL_stmt_fori *) $3;
new->label = $1;
new->body = $4;
$$ = (PLpgSQL_stmt *) new;
}
else if ($3->cmd_type == PLPGSQL_STMT_FORS)
{
PLpgSQL_stmt_fors *new;
new = (PLpgSQL_stmt_fors *) $3;
new->label = $1;
new->body = $4;
$$ = (PLpgSQL_stmt *) new;
}
else
{
$$.reverse = 0;
PLpgSQL_stmt_dynfors *new;
Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
new = (PLpgSQL_stmt_dynfors *) $3;
new->label = $1;
new->body = $4;
$$ = (PLpgSQL_stmt *) new;
}
/* close namespace started in opt_label */
plpgsql_ns_pop();
}
;
for_control : lno for_variable K_IN
{
int tok;
bool reverse = false;
bool execute = false;
PLpgSQL_expr *expr1;
/* check for REVERSE and EXECUTE */
tok = yylex();
if (tok == K_REVERSE)
{
reverse = true;
tok = yylex();
}
if (tok == K_EXECUTE)
execute = true;
else
plpgsql_push_back_token(tok);
}
$$.expr = plpgsql_read_expression(K_DOTDOT, "..");
}
;
/* Collect one or two expressions */
expr1 = read_sql_construct(K_DOTDOT,
K_LOOP,
"LOOP",
true,
"SELECT ",
&tok);
stmt_fors : opt_label K_FOR lno fors_target K_IN K_SELECT expr_until_loop loop_body
{
PLpgSQL_stmt_fors *new;
new = malloc(sizeof(PLpgSQL_stmt_fors));
memset(new, 0, sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
new->lineno = $3;
new->label = $1;
switch ($4->dtype)
if (tok == K_DOTDOT)
{
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);
/* Found .., so it must be an integer loop */
PLpgSQL_stmt_fori *new;
PLpgSQL_expr *expr2;
PLpgSQL_var *fvar;
expr2 = plpgsql_read_expression(K_LOOP, "LOOP");
if (execute)
{
plpgsql_error_lineno = $1;
yyerror("cannot specify EXECUTE in integer for-loop");
}
/* name should be malloc'd for use as varname */
fvar = (PLpgSQL_var *)
plpgsql_build_variable(strdup($2.name),
$2.lineno,
plpgsql_build_datatype(INT4OID,
-1),
true);
/* put the for-variable into the local block */
plpgsql_add_initdatums(NULL);
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;
}
new->query = $7;
new->body = $8;
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_dynfors : opt_label K_FOR lno fors_target K_IN K_EXECUTE expr_until_loop loop_body
{
PLpgSQL_stmt_dynfors *new;
new = malloc(sizeof(PLpgSQL_stmt_dynfors));
memset(new, 0, sizeof(PLpgSQL_stmt_dynfors));
new->cmd_type = PLPGSQL_STMT_DYNFORS;
new->lineno = $3;
new->label = $1;
switch ($4->dtype)
else if (execute)
{
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);
/* No .., so it must be a loop over rows */
PLpgSQL_stmt_dynfors *new;
if (reverse)
{
plpgsql_error_lineno = $1;
yyerror("cannot specify REVERSE in loop over rows");
}
new = malloc(sizeof(PLpgSQL_stmt_dynfors));
memset(new, 0, sizeof(PLpgSQL_stmt_dynfors));
new->cmd_type = PLPGSQL_STMT_DYNFORS;
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;
$$ = (PLpgSQL_stmt *) new;
}
new->query = $7;
new->body = $8;
else
{
/* 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));
new->cmd_type = 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");
}
/*
* Must get rid of the "SELECT " we prepended
* to expr1's text
*/
newquery = strdup(expr1->query + 7);
free(expr1->query);
expr1->query = newquery;
new->query = expr1;
$$ = (PLpgSQL_stmt *) new;
}
}
;
fors_target : T_RECORD
{ $$ = yylval.rec; }
for_variable : T_SCALAR
{
char *name;
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;
plpgsql_convert_ident(yytext, &name, 1);
$$.name = name;
$$.lineno = plpgsql_scanner_lineno();
$$.rec = yylval.rec;
$$.row = NULL;
}
| 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 :
PLpgSQL_expr *
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 *
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 *
read_sql_construct(int until,
int until2,
const char *expected,
bool isexpression,
const char *sqlstart)
const char *sqlstart,
int *endtoken)
{
int tok;
int lno;
@ -1554,6 +1632,8 @@ read_sql_construct(int until,
tok = yylex();
if (tok == until && parenlevel == 0)
break;
if (tok == until2 && parenlevel == 0)
break;
if (tok == '(' || tok == '[')
parenlevel++;
else if (tok == ')' || tok == ']')
@ -1586,7 +1666,6 @@ read_sql_construct(int until,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("missing \"%s\" at end of SQL statement",
expected)));
break;
}
if (plpgsql_SpaceScanned)
plpgsql_dstring_append(&ds, " ");
@ -1616,6 +1695,9 @@ read_sql_construct(int until,
}
}
if (endtoken)
*endtoken = tok;
expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = strdup(plpgsql_dstring_get(&ds));