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"> <chapter id="plpgsql">
@ -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;
<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>

View file

@ -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 @@
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);
} 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);
%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);
%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 ';'
{ $$ = $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 ';'
{ $$ = $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
} }
; ;
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)
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)
{ {
$$.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 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); 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 if (tok == K_DOTDOT)
{
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)
{ {
case PLPGSQL_DTYPE_REC: /* Found .., so it must be an integer loop */
new->rec = $4; PLpgSQL_stmt_fori *new;
break; PLpgSQL_expr *expr2;
case PLPGSQL_DTYPE_ROW: PLpgSQL_var *fvar;
new->row = (PLpgSQL_row *)$4;
break; expr2 = plpgsql_read_expression(K_LOOP, "LOOP");
default:
elog(ERROR, "unrecognized dtype: %d", if (execute)
$4->dtype); {
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; else if (execute)
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)
{ {
case PLPGSQL_DTYPE_REC: /* No .., so it must be a loop over rows */
new->rec = $4; PLpgSQL_stmt_dynfors *new;
break;
case PLPGSQL_DTYPE_ROW: if (reverse)
new->row = (PLpgSQL_row *)$4; {
break; plpgsql_error_lineno = $1;
default: yyerror("cannot specify REVERSE in loop over rows");
elog(ERROR, "unrecognized dtype: %d", }
$4->dtype);
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; 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));
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 for_variable : T_SCALAR
{ $$ = yylval.rec; } {
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 | 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_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,
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,
(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,
} }
} }
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));