Tweak plpgsql's expression reader to be smarter about parentheses and

to give more useful error messages.  Stephen Szabo's example of this
morning ('loop' used as a variable name inside a subselect) works
correctly now, and a FOR that is misinterpreted as an integer FOR will
draw 'missing .. at end of SQL expression', which is at least
marginally helpful.
This commit is contained in:
Tom Lane 2001-11-29 22:57:37 +00:00
parent d1a2a01f38
commit 8830ce54d6
2 changed files with 68 additions and 84 deletions

View File

@ -4,7 +4,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.28 2001/11/15 23:31:09 tgl Exp $ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.29 2001/11/29 22:57:37 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -39,7 +39,11 @@
#include "plpgsql.h" #include "plpgsql.h"
static PLpgSQL_expr *read_sqlstmt(int until, char *s, char *sqlstart); static PLpgSQL_expr *read_sql_construct(int until,
const char *expected,
bool isexpression,
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);
static PLpgSQL_stmt *make_fetch_stmt(void); static PLpgSQL_stmt *make_fetch_stmt(void);
@ -407,7 +411,7 @@ decl_cursor_query :
PLpgSQL_expr *query; PLpgSQL_expr *query;
plpgsql_ns_setlocal(false); plpgsql_ns_setlocal(false);
query = plpgsql_read_expression(';', ";"); query = read_sql_stmt("SELECT ");
plpgsql_ns_setlocal(true); plpgsql_ns_setlocal(true);
$$ = query; $$ = query;
@ -1002,74 +1006,20 @@ fori_varname : T_VARIABLE
fori_lower : fori_lower :
{ {
int tok; int tok;
int lno;
PLpgSQL_dstring ds;
int nparams = 0;
int params[1024];
char buf[32];
PLpgSQL_expr *expr;
int firsttok = 1;
lno = yylineno; tok = yylex();
plpgsql_dstring_init(&ds); if (tok == K_REVERSE)
plpgsql_dstring_append(&ds, "SELECT ");
$$.reverse = 0;
while((tok = yylex()) != K_DOTDOT)
{ {
if (firsttok) $$.reverse = 1;
{ }
firsttok = 0; else
if (tok == K_REVERSE) {
{ $$.reverse = 0;
$$.reverse = 1; plpgsql_push_back_token(tok);
continue;
}
}
if (tok == ';') break;
if (plpgsql_SpaceScanned)
plpgsql_dstring_append(&ds, " ");
switch (tok)
{
case T_VARIABLE:
params[nparams] = yylval.var->varno;
sprintf(buf, " $%d ", ++nparams);
plpgsql_dstring_append(&ds, buf);
break;
case T_RECFIELD:
params[nparams] = yylval.recfield->rfno;
sprintf(buf, " $%d ", ++nparams);
plpgsql_dstring_append(&ds, buf);
break;
case T_TGARGV:
params[nparams] = yylval.trigarg->dno;
sprintf(buf, " $%d ", ++nparams);
plpgsql_dstring_append(&ds, buf);
break;
default:
if (tok == 0)
{
plpgsql_error_lineno = lno;
elog(ERROR, "missing .. to terminate lower bound of for loop");
}
plpgsql_dstring_append(&ds, yytext);
break;
}
} }
expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int)); $$.expr = plpgsql_read_expression(K_DOTDOT, "..");
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = strdup(plpgsql_dstring_get(&ds));
expr->plan = NULL;
expr->nparams = nparams;
while(nparams-- > 0)
expr->params[nparams] = params[nparams];
plpgsql_dstring_free(&ds);
$$.expr = expr;
} }
stmt_fors : opt_label K_FOR lno fors_target K_IN K_SELECT expr_until_loop loop_body stmt_fors : opt_label K_FOR lno fors_target K_IN K_SELECT expr_until_loop loop_body
@ -1308,7 +1258,7 @@ stmt_execsql : execsql_start lno
new = malloc(sizeof(PLpgSQL_stmt_execsql)); new = malloc(sizeof(PLpgSQL_stmt_execsql));
new->cmd_type = PLPGSQL_STMT_EXECSQL; new->cmd_type = PLPGSQL_STMT_EXECSQL;
new->lineno = $2; new->lineno = $2;
new->sqlstmt = read_sqlstmt(';', ";", $1); new->sqlstmt = read_sql_stmt($1);
$$ = (PLpgSQL_stmt *)new; $$ = (PLpgSQL_stmt *)new;
} }
@ -1353,11 +1303,11 @@ stmt_open : K_OPEN lno cursor_varptr
switch (tok) switch (tok)
{ {
case K_SELECT: case K_SELECT:
new->query = plpgsql_read_expression(';', ";"); new->query = read_sql_stmt("SELECT ");
break; break;
case K_EXECUTE: case K_EXECUTE:
new->dynquery = plpgsql_read_expression(';', ";"); new->dynquery = read_sql_stmt("SELECT ");
break; break;
default: default:
@ -1380,7 +1330,7 @@ stmt_open : K_OPEN lno cursor_varptr
elog(ERROR, "cursor %s has arguments", $3->refname); elog(ERROR, "cursor %s has arguments", $3->refname);
} }
new->argquery = read_sqlstmt(';', ";", "SELECT "); new->argquery = read_sql_stmt("SELECT ");
/* Remove the trailing right paren, /* Remove the trailing right paren,
* because we want "select 1, 2", not * because we want "select 1, 2", not
* "select (1, 2)". * "select (1, 2)".
@ -1521,18 +1471,27 @@ lno :
PLpgSQL_expr * PLpgSQL_expr *
plpgsql_read_expression (int until, char *s) plpgsql_read_expression(int until, const char *expected)
{ {
return read_sqlstmt(until, s, "SELECT "); return read_sql_construct(until, expected, true, "SELECT ");
} }
static PLpgSQL_expr *
read_sql_stmt(const char *sqlstart)
{
return read_sql_construct(';', ";", false, sqlstart);
}
static PLpgSQL_expr * static PLpgSQL_expr *
read_sqlstmt (int until, char *s, char *sqlstart) read_sql_construct(int until,
const char *expected,
bool isexpression,
const char *sqlstart)
{ {
int tok; int tok;
int lno; int lno;
PLpgSQL_dstring ds; PLpgSQL_dstring ds;
int parenlevel = 0;
int nparams = 0; int nparams = 0;
int params[1024]; int params[1024];
char buf[32]; char buf[32];
@ -1540,20 +1499,43 @@ read_sqlstmt (int until, char *s, char *sqlstart)
lno = yylineno; lno = yylineno;
plpgsql_dstring_init(&ds); plpgsql_dstring_init(&ds);
plpgsql_dstring_append(&ds, sqlstart); plpgsql_dstring_append(&ds, (char *) sqlstart);
while((tok = yylex()) != until) for (;;)
{ {
if (tok == ';') break; tok = yylex();
if (tok == '(')
parenlevel++;
else if (tok == ')')
{
parenlevel--;
if (parenlevel < 0)
elog(ERROR, "mismatched parentheses");
}
else if (parenlevel == 0 && tok == until)
break;
/*
* End of function definition is an error, and we don't expect to
* hit a semicolon either (unless it's the until symbol, in which
* case we should have fallen out above).
*/
if (tok == 0 || tok == ';')
{
plpgsql_error_lineno = lno;
if (parenlevel != 0)
elog(ERROR, "mismatched parentheses");
if (isexpression)
elog(ERROR, "missing %s at end of SQL expression",
expected);
else
elog(ERROR, "missing %s at end of SQL statement",
expected);
break;
}
if (plpgsql_SpaceScanned) if (plpgsql_SpaceScanned)
plpgsql_dstring_append(&ds, " "); plpgsql_dstring_append(&ds, " ");
switch (tok) switch (tok)
{ {
case 0:
plpgsql_error_lineno = lno;
elog(ERROR, "missing %s at end of SQL statement", s);
break;
case T_VARIABLE: case T_VARIABLE:
params[nparams] = yylval.var->varno; params[nparams] = yylval.var->varno;
sprintf(buf, " $%d ", ++nparams); sprintf(buf, " $%d ", ++nparams);
@ -1618,6 +1600,8 @@ read_datatype(int tok)
if (tok == 0) if (tok == 0)
{ {
plpgsql_error_lineno = lno; plpgsql_error_lineno = lno;
if (parenlevel != 0)
elog(ERROR, "mismatched parentheses");
elog(ERROR, "incomplete datatype declaration"); elog(ERROR, "incomplete datatype declaration");
} }
/* Possible followers for datatype in a declaration */ /* Possible followers for datatype in a declaration */

View File

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.23 2001/11/15 23:31:09 tgl Exp $ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.24 2001/11/29 22:57:37 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -606,7 +606,7 @@ extern void plpgsql_dumptree(PLpgSQL_function * func);
* Externs in gram.y and scan.l * Externs in gram.y and scan.l
* ---------- * ----------
*/ */
extern PLpgSQL_expr *plpgsql_read_expression(int until, char *s); extern PLpgSQL_expr *plpgsql_read_expression(int until, const char *expected);
extern int plpgsql_yyparse(void); extern int plpgsql_yyparse(void);
extern int plpgsql_base_yylex(void); extern int plpgsql_base_yylex(void);
extern int plpgsql_yylex(void); extern int plpgsql_yylex(void);