Rearrange plpgsql parsing to simplify and speed it up a bit.

* Pull the responsibility for %TYPE and %ROWTYPE out of the scanner,
letting read_datatype manage it instead.

* Avoid unnecessary scanner-driven lookups of plpgsql variables in
places where it's not needed, which is actually most of the time;
we do not need it in DECLARE sections nor in text that is a SQL
query or expression.

* Rationalize the set of token types returned by the scanner:
distinguishing T_SCALAR, T_RECORD, T_ROW seems to complicate the grammar
in more places than it simplifies it, so merge these into one
token type T_DATUM; but split T_ERROR into T_DBLWORD and T_TRIPWORD
for clarity and simplicity of later processing.

Some of this will need to be revisited again when we try to make
plpgsql use the core scanner, but this patch gets some of the bigger
stumbling blocks out of the way.
This commit is contained in:
Tom Lane 2009-11-07 00:52:26 +00:00
parent b79f49c780
commit f2b7692e75
5 changed files with 372 additions and 373 deletions

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.131 2009/11/06 18:37:54 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.132 2009/11/07 00:52:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -109,11 +109,8 @@ static List *read_raise_options(void);
} loop_body;
List *list;
PLpgSQL_type *dtype;
PLpgSQL_datum *scalar; /* a VAR or RECFIELD */
PLpgSQL_variable *variable; /* a VAR, REC, or ROW */
PLpgSQL_datum *datum;
PLpgSQL_var *var;
PLpgSQL_row *row;
PLpgSQL_rec *rec;
PLpgSQL_expr *expr;
PLpgSQL_stmt *stmt;
PLpgSQL_stmt_block *program;
@ -131,7 +128,7 @@ static List *read_raise_options(void);
%type <boolean> decl_const decl_notnull exit_type
%type <expr> decl_defval decl_cursor_query
%type <dtype> decl_datatype
%type <row> decl_cursor_args
%type <datum> decl_cursor_args
%type <list> decl_cursor_arglist
%type <nsitem> decl_aliasitem
%type <str> decl_stmts decl_stmt
@ -142,7 +139,7 @@ static List *read_raise_options(void);
%type <ival> assign_var
%type <var> cursor_variable
%type <variable> decl_cursor_arg
%type <datum> decl_cursor_arg
%type <forvariable> for_variable
%type <stmt> for_control
@ -234,12 +231,10 @@ static List *read_raise_options(void);
*/
%token T_STRING
%token T_NUMBER
%token T_SCALAR /* a VAR or RECFIELD */
%token T_ROW
%token T_RECORD
%token T_DTYPE
%token T_WORD
%token T_ERROR
%token T_DATUM /* a VAR, ROW, REC, or RECFIELD variable */
%token T_WORD /* unrecognized simple identifier */
%token T_DBLWORD /* unrecognized ident.ident */
%token T_TRIPWORD /* unrecognized ident.ident.ident */
%token O_OPTION
%token O_DUMP
@ -294,21 +289,22 @@ pl_block : decl_sect K_BEGIN lno proc_sect exception_sect K_END opt_label
decl_sect : opt_block_label
{
plpgsql_ns_setlocal(false);
/* done with decls, so resume identifier lookup */
plpgsql_LookupIdentifiers = true;
$$.label = $1;
$$.n_initvars = 0;
$$.initvarnos = NULL;
}
| opt_block_label decl_start
{
plpgsql_ns_setlocal(false);
plpgsql_LookupIdentifiers = true;
$$.label = $1;
$$.n_initvars = 0;
$$.initvarnos = NULL;
}
| opt_block_label decl_start decl_stmts
{
plpgsql_ns_setlocal(false);
plpgsql_LookupIdentifiers = true;
if ($3 != NULL)
$$.label = $3;
else
@ -322,8 +318,11 @@ decl_start : K_DECLARE
{
/* Forget any variables created before block */
plpgsql_add_initdatums(NULL);
/* Make variable names be local to block */
plpgsql_ns_setlocal(true);
/*
* Disable scanner lookup of identifiers while
* we process the decl_stmts
*/
plpgsql_LookupIdentifiers = false;
}
;
@ -447,13 +446,7 @@ opt_scrollable :
decl_cursor_query :
{
PLpgSQL_expr *query;
plpgsql_ns_setlocal(false);
query = read_sql_stmt("");
plpgsql_ns_setlocal(true);
$$ = query;
$$ = read_sql_stmt("");
}
;
@ -486,7 +479,7 @@ decl_cursor_args :
list_free($2);
plpgsql_adddatum((PLpgSQL_datum *) new);
$$ = new;
$$ = (PLpgSQL_datum *) new;
}
;
@ -502,26 +495,24 @@ decl_cursor_arglist : decl_cursor_arg
decl_cursor_arg : decl_varname decl_datatype
{
$$ = plpgsql_build_variable($1.name, $1.lineno,
$2, true);
$$ = (PLpgSQL_datum *)
plpgsql_build_variable($1.name, $1.lineno,
$2, true);
}
;
decl_is_for : K_IS | /* Oracle */
K_FOR; /* SQL standard */
decl_aliasitem : any_identifier
decl_aliasitem : T_WORD
{
char *name;
char *name[1];
PLpgSQL_nsitem *nsi;
/* XXX should allow block-label-qualified names */
plpgsql_convert_ident($1, &name, 1);
plpgsql_convert_ident(yytext, name, 1);
plpgsql_ns_setlocal(false);
nsi = plpgsql_ns_lookup(plpgsql_ns_top(),
name, NULL, NULL,
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
name[0], NULL, NULL,
NULL);
if (nsi == NULL)
{
@ -529,12 +520,34 @@ decl_aliasitem : any_identifier
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("variable \"%s\" does not exist",
name)));
name[0])));
}
plpgsql_ns_setlocal(true);
pfree(name[0]);
pfree(name);
$$ = nsi;
}
| T_DBLWORD
{
char *name[2];
PLpgSQL_nsitem *nsi;
plpgsql_convert_ident(yytext, name, 2);
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
name[0], name[1], NULL,
NULL);
if (nsi == NULL)
{
plpgsql_error_lineno = plpgsql_scanner_lineno();
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("variable \"%s.%s\" does not exist",
name[0], name[1])));
}
pfree(name[0]);
pfree(name[1]);
$$ = nsi;
}
@ -547,24 +560,14 @@ decl_varname : T_WORD
plpgsql_convert_ident(yytext, &name, 1);
$$.name = name;
$$.lineno = plpgsql_scanner_lineno();
}
| T_SCALAR
{
/*
* Since the scanner is only searching the topmost
* namespace level, getting T_SCALAR etc can only
* happen if the name is already declared in this
* block.
* Check to make sure name isn't already declared
* in the current block.
*/
yyerror("duplicate declaration");
}
| T_ROW
{
yyerror("duplicate declaration");
}
| T_RECORD
{
yyerror("duplicate declaration");
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
name, NULL, NULL,
NULL) != NULL)
yyerror("duplicate declaration");
}
;
@ -595,9 +598,7 @@ decl_defval : ';'
{ $$ = NULL; }
| decl_defkey
{
plpgsql_ns_setlocal(false);
$$ = plpgsql_read_expression(';', ";");
plpgsql_ns_setlocal(true);
}
;
@ -739,40 +740,32 @@ getdiag_kind : K_ROW_COUNT
}
;
getdiag_target : T_SCALAR
getdiag_target : T_DATUM
{
check_assignable(yylval.scalar);
$$ = yylval.scalar->dno;
}
| T_ROW
{
yyerror("expected an integer variable");
}
| T_RECORD
{
yyerror("expected an integer variable");
check_assignable(yylval.datum);
if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.datum->dtype == PLPGSQL_DTYPE_REC)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
yytext)));
$$ = yylval.datum->dno;
}
| T_WORD
{
yyerror("expected an integer variable");
/* just to give a better message than "syntax error" */
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a known variable",
yytext)));
}
;
assign_var : T_SCALAR
assign_var : T_DATUM
{
check_assignable(yylval.scalar);
$$ = yylval.scalar->dno;
}
| T_ROW
{
check_assignable((PLpgSQL_datum *) yylval.row);
$$ = yylval.row->dno;
}
| T_RECORD
{
check_assignable((PLpgSQL_datum *) yylval.rec);
$$ = yylval.rec->dno;
check_assignable(yylval.datum);
$$ = yylval.datum->dno;
}
| assign_var '[' expr_until_rightbracket
{
@ -783,7 +776,7 @@ assign_var : T_SCALAR
new->subscript = $3;
new->arrayparentno = $1;
plpgsql_adddatum((PLpgSQL_datum *)new);
plpgsql_adddatum((PLpgSQL_datum *) new);
$$ = new->dno;
}
@ -1026,13 +1019,13 @@ for_control :
$$ = (PLpgSQL_stmt *) new;
}
else if (tok == T_SCALAR &&
yylval.scalar->dtype == PLPGSQL_DTYPE_VAR &&
((PLpgSQL_var *) yylval.scalar)->datatype->typoid == REFCURSOROID)
else if (tok == T_DATUM &&
yylval.datum->dtype == PLPGSQL_DTYPE_VAR &&
((PLpgSQL_var *) yylval.datum)->datatype->typoid == REFCURSOROID)
{
/* It's FOR var IN cursor */
PLpgSQL_stmt_forc *new;
PLpgSQL_var *cursor = (PLpgSQL_var *) yylval.scalar;
PLpgSQL_var *cursor = (PLpgSQL_var *) yylval.datum;
char *varname;
new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc));
@ -1227,20 +1220,36 @@ for_control :
* Note that the non-error result of this case sets *both* $$.scalar and
* $$.row; see the for_control production.
*/
for_variable : T_SCALAR
for_variable : T_DATUM
{
int tok;
$$.name = pstrdup(yytext);
$$.lineno = plpgsql_scanner_lineno();
$$.scalar = yylval.scalar;
$$.rec = NULL;
$$.row = NULL;
/* check for comma-separated list */
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == ',')
$$.row = read_into_scalar_list($$.name, $$.scalar);
if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW)
{
$$.scalar = NULL;
$$.rec = NULL;
$$.row = (PLpgSQL_row *) yylval.datum;
}
else if (yylval.datum->dtype == PLPGSQL_DTYPE_REC)
{
$$.scalar = NULL;
$$.rec = (PLpgSQL_rec *) yylval.datum;
$$.row = NULL;
}
else
{
int tok;
$$.scalar = yylval.datum;
$$.rec = NULL;
$$.row = NULL;
/* check for comma-separated list */
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == ',')
$$.row = read_into_scalar_list($$.name,
$$.scalar);
}
}
| T_WORD
{
@ -1259,26 +1268,10 @@ for_variable : T_SCALAR
plpgsql_error_lineno = $$.lineno;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
errmsg("\"%s\" is not a known variable",
$$.name)));
}
}
| T_RECORD
{
$$.name = pstrdup(yytext);
$$.lineno = plpgsql_scanner_lineno();
$$.scalar = NULL;
$$.rec = yylval.rec;
$$.row = NULL;
}
| T_ROW
{
$$.name = pstrdup(yytext);
$$.lineno = plpgsql_scanner_lineno();
$$.scalar = NULL;
$$.row = yylval.row;
$$.rec = NULL;
}
;
stmt_exit : exit_type lno opt_label opt_exitcond
@ -1484,12 +1477,18 @@ stmt_execsql : execsql_start lno
}
;
/* T_WORD+T_ERROR match any otherwise-unrecognized starting keyword */
/*
* T_WORD+T_DBLWORD+T_TRIPWORD match any initial identifier that is not a
* known plpgsql variable. The latter two cases are probably syntax errors,
* but we'll let the core parser decide that.
*/
execsql_start : K_INSERT
{ $$ = pstrdup(yytext); }
| T_WORD
{ $$ = pstrdup(yytext); }
| T_ERROR
| T_DBLWORD
{ $$ = pstrdup(yytext); }
| T_TRIPWORD
{ $$ = pstrdup(yytext); }
;
@ -1667,32 +1666,28 @@ stmt_null : K_NULL ';'
}
;
cursor_variable : T_SCALAR
cursor_variable : T_DATUM
{
if (yylval.scalar->dtype != PLPGSQL_DTYPE_VAR)
if (yylval.datum->dtype != PLPGSQL_DTYPE_VAR)
yyerror("cursor variable must be a simple variable");
if (((PLpgSQL_var *) yylval.scalar)->datatype->typoid != REFCURSOROID)
if (((PLpgSQL_var *) yylval.datum)->datatype->typoid != REFCURSOROID)
{
plpgsql_error_lineno = plpgsql_scanner_lineno();
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("variable \"%s\" must be of type cursor or refcursor",
((PLpgSQL_var *) yylval.scalar)->refname)));
((PLpgSQL_var *) yylval.datum)->refname)));
}
$$ = (PLpgSQL_var *) yylval.scalar;
}
| T_ROW
{
yyerror("expected a cursor or refcursor variable");
}
| T_RECORD
{
yyerror("expected a cursor or refcursor variable");
$$ = (PLpgSQL_var *) yylval.datum;
}
| T_WORD
{
yyerror("expected a cursor or refcursor variable");
/* just to give a better message than "syntax error" */
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a known variable",
yytext)));
}
;
@ -1852,21 +1847,13 @@ opt_exitcond : ';'
;
/*
* need all the options because scanner will have tried to resolve as variable
* need both options because scanner will have tried to resolve as variable
*/
any_identifier : T_WORD
{
$$ = yytext;
}
| T_SCALAR
{
$$ = yytext;
}
| T_RECORD
{
$$ = yytext;
}
| T_ROW
| T_DATUM
{
$$ = yytext;
}
@ -1941,6 +1928,7 @@ read_sql_construct(int until,
int tok;
int lno;
StringInfoData ds;
bool save_LookupIdentifiers;
int parenlevel = 0;
PLpgSQL_expr *expr;
@ -1948,6 +1936,10 @@ read_sql_construct(int until,
initStringInfo(&ds);
appendStringInfoString(&ds, sqlstart);
/* no need to lookup identifiers within the SQL text */
save_LookupIdentifiers = plpgsql_LookupIdentifiers;
plpgsql_LookupIdentifiers = false;
for (;;)
{
tok = yylex();
@ -1992,6 +1984,8 @@ read_sql_construct(int until,
appendStringInfoString(&ds, yytext);
}
plpgsql_LookupIdentifiers = save_LookupIdentifiers;
if (endtoken)
*endtoken = tok;
@ -2019,19 +2013,100 @@ read_datatype(int tok)
bool needspace = false;
int parenlevel = 0;
/* Should always be called with LookupIdentifiers off */
Assert(!plpgsql_LookupIdentifiers);
lno = plpgsql_scanner_lineno();
initStringInfo(&ds);
/* Often there will be a lookahead token, but if not, get one */
if (tok == YYEMPTY)
tok = yylex();
if (tok == T_DTYPE)
/*
* If we have a single, double, or triple identifier, check for %TYPE
* and %ROWTYPE constructs.
*/
if (tok == T_WORD)
{
/* lexer found word%TYPE and did its thing already */
return yylval.dtype;
appendStringInfoString(&ds, yytext);
tok = yylex();
if (tok == '%')
{
tok = yylex();
if (pg_strcasecmp(yytext, "type") == 0)
{
result = plpgsql_parse_wordtype(ds.data);
if (result)
{
pfree(ds.data);
return result;
}
}
else if (pg_strcasecmp(yytext, "rowtype") == 0)
{
result = plpgsql_parse_wordrowtype(ds.data);
if (result)
{
pfree(ds.data);
return result;
}
}
appendStringInfoString(&ds, " %");
}
needspace = true;
}
else if (tok == T_DBLWORD)
{
appendStringInfoString(&ds, yytext);
tok = yylex();
if (tok == '%')
{
tok = yylex();
if (pg_strcasecmp(yytext, "type") == 0)
{
result = plpgsql_parse_dblwordtype(ds.data);
if (result)
{
pfree(ds.data);
return result;
}
}
else if (pg_strcasecmp(yytext, "rowtype") == 0)
{
result = plpgsql_parse_dblwordrowtype(ds.data);
if (result)
{
pfree(ds.data);
return result;
}
}
appendStringInfoString(&ds, " %");
}
needspace = true;
}
else if (tok == T_TRIPWORD)
{
appendStringInfoString(&ds, yytext);
tok = yylex();
if (tok == '%')
{
tok = yylex();
if (pg_strcasecmp(yytext, "type") == 0)
{
result = plpgsql_parse_tripwordtype(ds.data);
if (result)
{
pfree(ds.data);
return result;
}
}
/* there's no tripword rowtype construct */
appendStringInfoString(&ds, " %");
}
needspace = true;
}
initStringInfo(&ds);
while (tok != ';')
{
@ -2080,6 +2155,7 @@ static PLpgSQL_stmt *
make_execsql_stmt(const char *sqlstart, int lineno)
{
StringInfoData ds;
bool save_LookupIdentifiers;
PLpgSQL_stmt_execsql *execsql;
PLpgSQL_expr *expr;
PLpgSQL_row *row = NULL;
@ -2092,6 +2168,10 @@ make_execsql_stmt(const char *sqlstart, int lineno)
initStringInfo(&ds);
appendStringInfoString(&ds, sqlstart);
/* no need to lookup identifiers within the SQL text */
save_LookupIdentifiers = plpgsql_LookupIdentifiers;
plpgsql_LookupIdentifiers = false;
/*
* We have to special-case the sequence INSERT INTO, because we don't want
* that to be taken as an INTO-variables clause. Fortunately, this is the
@ -2119,7 +2199,9 @@ make_execsql_stmt(const char *sqlstart, int lineno)
if (have_into)
yyerror("INTO specified more than once");
have_into = true;
plpgsql_LookupIdentifiers = true;
read_into_target(&rec, &row, &have_strict);
plpgsql_LookupIdentifiers = false;
continue;
}
@ -2128,6 +2210,8 @@ make_execsql_stmt(const char *sqlstart, int lineno)
appendStringInfoString(&ds, yytext);
}
plpgsql_LookupIdentifiers = save_LookupIdentifiers;
expr = palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(ds.data);
@ -2233,7 +2317,7 @@ read_fetch_direction(void)
/* empty direction */
check_FROM = false;
}
else if (tok == T_SCALAR)
else if (tok == T_DATUM)
{
/* Assume there's no direction clause and tok is a cursor name */
plpgsql_push_back_token(tok);
@ -2245,7 +2329,7 @@ read_fetch_direction(void)
* Assume it's a count expression with no preceding keyword.
* Note: we allow this syntax because core SQL does, but we don't
* document it because of the ambiguity with the omitted-direction
* case. For instance, "MOVE n IN c" will fail if n is a scalar.
* case. For instance, "MOVE n IN c" will fail if n is a variable.
* Perhaps this can be improved someday, but it's hardly worth a
* lot of work.
*/
@ -2342,12 +2426,12 @@ make_return_stmt(int lineno)
/* we allow this to support RETURN NULL in triggers */
break;
case T_ROW:
new->retvarno = yylval.row->dno;
break;
case T_RECORD:
new->retvarno = yylval.rec->dno;
case T_DATUM:
if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.datum->dtype == PLPGSQL_DTYPE_REC)
new->retvarno = yylval.datum->dno;
else
yyerror("RETURN must specify a record or row variable in function returning row");
break;
default:
@ -2395,12 +2479,12 @@ make_return_next_stmt(int lineno)
{
switch (yylex())
{
case T_ROW:
new->retvarno = yylval.row->dno;
break;
case T_RECORD:
new->retvarno = yylval.rec->dno;
case T_DATUM:
if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.datum->dtype == PLPGSQL_DTYPE_REC)
new->retvarno = yylval.datum->dno;
else
yyerror("RETURN NEXT must specify a record or row variable in function returning row");
break;
default:
@ -2517,18 +2601,21 @@ read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict)
switch (tok)
{
case T_ROW:
*row = yylval.row;
check_assignable((PLpgSQL_datum *) *row);
break;
case T_RECORD:
*rec = yylval.rec;
check_assignable((PLpgSQL_datum *) *rec);
break;
case T_SCALAR:
*row = read_into_scalar_list(yytext, yylval.scalar);
case T_DATUM:
if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW)
{
check_assignable(yylval.datum);
*row = (PLpgSQL_row *) yylval.datum;
}
else if (yylval.datum->dtype == PLPGSQL_DTYPE_REC)
{
check_assignable(yylval.datum);
*rec = (PLpgSQL_rec *) yylval.datum;
}
else
{
*row = read_into_scalar_list(yytext, yylval.datum);
}
break;
default:
@ -2576,17 +2663,23 @@ read_into_scalar_list(const char *initial_name,
tok = yylex();
switch(tok)
{
case T_SCALAR:
check_assignable(yylval.scalar);
case T_DATUM:
check_assignable(yylval.datum);
if (yylval.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.datum->dtype == PLPGSQL_DTYPE_REC)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
yytext)));
fieldnames[nfields] = pstrdup(yytext);
varnos[nfields++] = yylval.scalar->dno;
varnos[nfields++] = yylval.datum->dno;
break;
default:
plpgsql_error_lineno = plpgsql_scanner_lineno();
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
errmsg("\"%s\" is not a known variable",
yytext)));
}
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.141 2009/11/06 18:37:54 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.142 2009/11/07 00:52:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1006,7 +1006,7 @@ plpgsql_param_ref(ParseState *pstate, ParamRef *pref)
snprintf(pname, sizeof(pname), "$%d", pref->number);
nse = plpgsql_ns_lookup(expr->ns,
nse = plpgsql_ns_lookup(expr->ns, false,
pname, NULL, NULL,
NULL);
@ -1120,7 +1120,7 @@ resolve_column_ref(PLpgSQL_expr *expr, ColumnRef *cref)
return NULL;
}
nse = plpgsql_ns_lookup(expr->ns,
nse = plpgsql_ns_lookup(expr->ns, false,
name1, name2, name3,
&nnames);
@ -1200,7 +1200,7 @@ resolve_column_ref(PLpgSQL_expr *expr, ColumnRef *cref)
}
break;
default:
elog(ERROR, "unrecognized plpgsql itemtype");
elog(ERROR, "unrecognized plpgsql itemtype: %d", nse->itemtype);
}
/* Name format doesn't match the plpgsql variable type */
@ -1259,7 +1259,7 @@ plpgsql_parse_word(const char *word)
/*
* Do a lookup in the current namespace stack
*/
nse = plpgsql_ns_lookup(plpgsql_ns_top(),
nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
cp[0], NULL, NULL,
NULL);
pfree(cp[0]);
@ -1269,19 +1269,13 @@ plpgsql_parse_word(const char *word)
switch (nse->itemtype)
{
case PLPGSQL_NSTYPE_VAR:
plpgsql_yylval.scalar = plpgsql_Datums[nse->itemno];
return T_SCALAR;
case PLPGSQL_NSTYPE_REC:
plpgsql_yylval.rec = (PLpgSQL_rec *) (plpgsql_Datums[nse->itemno]);
return T_RECORD;
case PLPGSQL_NSTYPE_ROW:
plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[nse->itemno]);
return T_ROW;
case PLPGSQL_NSTYPE_REC:
plpgsql_yylval.datum = plpgsql_Datums[nse->itemno];
return T_DATUM;
default:
return T_ERROR;
elog(ERROR, "unrecognized plpgsql itemtype: %d", nse->itemtype);
}
}
@ -1311,24 +1305,24 @@ plpgsql_parse_dblword(const char *word)
/*
* Do a lookup in the current namespace stack
*/
ns = plpgsql_ns_lookup(plpgsql_ns_top(),
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
cp[0], cp[1], NULL,
&nnames);
if (ns == NULL)
{
pfree(cp[0]);
pfree(cp[1]);
return T_ERROR;
return T_DBLWORD;
}
switch (ns->itemtype)
{
case PLPGSQL_NSTYPE_VAR:
/* Block-qualified reference to scalar variable. */
plpgsql_yylval.scalar = plpgsql_Datums[ns->itemno];
plpgsql_yylval.datum = plpgsql_Datums[ns->itemno];
pfree(cp[0]);
pfree(cp[1]);
return T_SCALAR;
return T_DATUM;
case PLPGSQL_NSTYPE_REC:
if (nnames == 1)
@ -1346,19 +1340,19 @@ plpgsql_parse_dblword(const char *word)
plpgsql_adddatum((PLpgSQL_datum *) new);
plpgsql_yylval.scalar = (PLpgSQL_datum *) new;
plpgsql_yylval.datum = (PLpgSQL_datum *) new;
pfree(cp[0]);
pfree(cp[1]);
return T_SCALAR;
return T_DATUM;
}
else
{
/* Block-qualified reference to record variable. */
plpgsql_yylval.rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
plpgsql_yylval.datum = plpgsql_Datums[ns->itemno];
pfree(cp[0]);
pfree(cp[1]);
return T_RECORD;
return T_DATUM;
}
case PLPGSQL_NSTYPE_ROW:
@ -1377,10 +1371,10 @@ plpgsql_parse_dblword(const char *word)
if (row->fieldnames[i] &&
strcmp(row->fieldnames[i], cp[1]) == 0)
{
plpgsql_yylval.scalar = plpgsql_Datums[row->varnos[i]];
plpgsql_yylval.datum = plpgsql_Datums[row->varnos[i]];
pfree(cp[0]);
pfree(cp[1]);
return T_SCALAR;
return T_DATUM;
}
}
ereport(ERROR,
@ -1391,10 +1385,10 @@ plpgsql_parse_dblword(const char *word)
else
{
/* Block-qualified reference to row variable. */
plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[ns->itemno]);
plpgsql_yylval.datum = plpgsql_Datums[ns->itemno];
pfree(cp[0]);
pfree(cp[1]);
return T_ROW;
return T_DATUM;
}
default:
@ -1403,7 +1397,7 @@ plpgsql_parse_dblword(const char *word)
pfree(cp[0]);
pfree(cp[1]);
return T_ERROR;
return T_DBLWORD;
}
@ -1426,7 +1420,7 @@ plpgsql_parse_tripword(const char *word)
* Do a lookup in the current namespace stack. Must find a qualified
* reference.
*/
ns = plpgsql_ns_lookup(plpgsql_ns_top(),
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
cp[0], cp[1], cp[2],
&nnames);
if (ns == NULL || nnames != 2)
@ -1434,7 +1428,7 @@ plpgsql_parse_tripword(const char *word)
pfree(cp[0]);
pfree(cp[1]);
pfree(cp[2]);
return T_ERROR;
return T_TRIPWORD;
}
switch (ns->itemtype)
@ -1454,13 +1448,13 @@ plpgsql_parse_tripword(const char *word)
plpgsql_adddatum((PLpgSQL_datum *) new);
plpgsql_yylval.scalar = (PLpgSQL_datum *) new;
plpgsql_yylval.datum = (PLpgSQL_datum *) new;
pfree(cp[0]);
pfree(cp[1]);
pfree(cp[2]);
return T_SCALAR;
return T_DATUM;
}
case PLPGSQL_NSTYPE_ROW:
@ -1478,13 +1472,13 @@ plpgsql_parse_tripword(const char *word)
if (row->fieldnames[i] &&
strcmp(row->fieldnames[i], cp[2]) == 0)
{
plpgsql_yylval.scalar = plpgsql_Datums[row->varnos[i]];
plpgsql_yylval.datum = plpgsql_Datums[row->varnos[i]];
pfree(cp[0]);
pfree(cp[1]);
pfree(cp[2]);
return T_SCALAR;
return T_DATUM;
}
}
ereport(ERROR,
@ -1500,41 +1494,34 @@ plpgsql_parse_tripword(const char *word)
pfree(cp[0]);
pfree(cp[1]);
pfree(cp[2]);
return T_ERROR;
return T_TRIPWORD;
}
/* ----------
* plpgsql_parse_wordtype The scanner found word%TYPE. word can be
* a variable name or a basetype.
*
* Returns datatype struct, or NULL if no match found for word.
* ----------
*/
int
plpgsql_parse_wordtype(char *word)
PLpgSQL_type *
plpgsql_parse_wordtype(const char *word)
{
PLpgSQL_type *dtype;
PLpgSQL_nsitem *nse;
bool old_nsstate;
HeapTuple typeTup;
char *cp[2];
int i;
char *cp[1];
/* Do case conversion and word separation */
/* We convert %type to .type momentarily to keep converter happy */
i = strlen(word) - 5;
Assert(word[i] == '%');
word[i] = '.';
plpgsql_convert_ident(word, cp, 2);
word[i] = '%';
pfree(cp[1]);
plpgsql_convert_ident(word, cp, 1);
/*
* Do a lookup in the current namespace stack. Ensure we scan all levels.
* Do a lookup in the current namespace stack
*/
old_nsstate = plpgsql_ns_setlocal(false);
nse = plpgsql_ns_lookup(plpgsql_ns_top(),
nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
cp[0], NULL, NULL,
NULL);
plpgsql_ns_setlocal(old_nsstate);
if (nse != NULL)
{
@ -1542,13 +1529,12 @@ plpgsql_parse_wordtype(char *word)
switch (nse->itemtype)
{
case PLPGSQL_NSTYPE_VAR:
plpgsql_yylval.dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
return T_DTYPE;
return ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
/* XXX perhaps allow REC here? */
/* XXX perhaps allow REC/ROW here? */
default:
return T_ERROR;
return NULL;
}
}
@ -1566,14 +1552,14 @@ plpgsql_parse_wordtype(char *word)
{
ReleaseSysCache(typeTup);
pfree(cp[0]);
return T_ERROR;
return NULL;
}
plpgsql_yylval.dtype = build_datatype(typeTup, -1);
dtype = build_datatype(typeTup, -1);
ReleaseSysCache(typeTup);
pfree(cp[0]);
return T_DTYPE;
return dtype;
}
/*
@ -1581,7 +1567,7 @@ plpgsql_parse_wordtype(char *word)
* us.
*/
pfree(cp[0]);
return T_ERROR;
return NULL;
}
@ -1589,49 +1575,38 @@ plpgsql_parse_wordtype(char *word)
* plpgsql_parse_dblwordtype Same lookup for word.word%TYPE
* ----------
*/
int
plpgsql_parse_dblwordtype(char *word)
PLpgSQL_type *
plpgsql_parse_dblwordtype(const char *word)
{
PLpgSQL_type *dtype = NULL;
PLpgSQL_nsitem *nse;
bool old_nsstate;
Oid classOid;
HeapTuple classtup = NULL;
HeapTuple attrtup = NULL;
HeapTuple typetup = NULL;
Form_pg_class classStruct;
Form_pg_attribute attrStruct;
char *cp[3];
int i;
char *cp[2];
MemoryContext oldCxt;
int result = T_ERROR;
/* Avoid memory leaks in the long-term function context */
oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
/* Do case conversion and word separation */
/* We convert %type to .type momentarily to keep converter happy */
i = strlen(word) - 5;
Assert(word[i] == '%');
word[i] = '.';
plpgsql_convert_ident(word, cp, 3);
word[i] = '%';
pfree(cp[2]);
plpgsql_convert_ident(word, cp, 2);
/*
* Do a lookup in the current namespace stack. Ensure we scan all levels.
* Do a lookup in the current namespace stack.
* We don't need to check number of names matched, because we will only
* consider scalar variables.
*/
old_nsstate = plpgsql_ns_setlocal(false);
nse = plpgsql_ns_lookup(plpgsql_ns_top(),
nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
cp[0], cp[1], NULL,
NULL);
plpgsql_ns_setlocal(old_nsstate);
if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR)
{
plpgsql_yylval.dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
result = T_DTYPE;
dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
goto done;
}
@ -1677,9 +1652,8 @@ plpgsql_parse_dblwordtype(char *word)
* return it
*/
MemoryContextSwitchTo(oldCxt);
plpgsql_yylval.dtype = build_datatype(typetup, attrStruct->atttypmod);
dtype = build_datatype(typetup, attrStruct->atttypmod);
MemoryContextSwitchTo(compile_tmp_cxt);
result = T_DTYPE;
done:
if (HeapTupleIsValid(classtup))
@ -1690,39 +1664,32 @@ done:
ReleaseSysCache(typetup);
MemoryContextSwitchTo(oldCxt);
return result;
return dtype;
}
/* ----------
* plpgsql_parse_tripwordtype Same lookup for word.word.word%TYPE
* ----------
*/
int
plpgsql_parse_tripwordtype(char *word)
PLpgSQL_type *
plpgsql_parse_tripwordtype(const char *word)
{
PLpgSQL_type *dtype = NULL;
Oid classOid;
HeapTuple classtup = NULL;
HeapTuple attrtup = NULL;
HeapTuple typetup = NULL;
Form_pg_class classStruct;
Form_pg_attribute attrStruct;
char *cp[4];
int i;
char *cp[3];
RangeVar *relvar;
MemoryContext oldCxt;
int result = T_ERROR;
/* Avoid memory leaks in the long-term function context */
oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
/* Do case conversion and word separation */
/* We convert %type to .type momentarily to keep converter happy */
i = strlen(word) - 5;
Assert(word[i] == '%');
word[i] = '.';
plpgsql_convert_ident(word, cp, 4);
word[i] = '%';
pfree(cp[3]);
plpgsql_convert_ident(word, cp, 3);
relvar = makeRangeVar(cp[0], cp[1], -1);
classOid = RangeVarGetRelid(relvar, true);
@ -1764,9 +1731,8 @@ plpgsql_parse_tripwordtype(char *word)
* return it
*/
MemoryContextSwitchTo(oldCxt);
plpgsql_yylval.dtype = build_datatype(typetup, attrStruct->atttypmod);
dtype = build_datatype(typetup, attrStruct->atttypmod);
MemoryContextSwitchTo(compile_tmp_cxt);
result = T_DTYPE;
done:
if (HeapTupleIsValid(classtup))
@ -1777,7 +1743,7 @@ done:
ReleaseSysCache(typetup);
MemoryContextSwitchTo(oldCxt);
return result;
return dtype;
}
/* ----------
@ -1785,20 +1751,15 @@ done:
* So word must be a table name.
* ----------
*/
int
plpgsql_parse_wordrowtype(char *word)
PLpgSQL_type *
plpgsql_parse_wordrowtype(const char *word)
{
PLpgSQL_type *dtype;
Oid classOid;
char *cp[2];
int i;
char *cp[1];
/* Do case conversion and word separation */
/* We convert %rowtype to .rowtype momentarily to keep converter happy */
i = strlen(word) - 8;
Assert(word[i] == '%');
word[i] = '.';
plpgsql_convert_ident(word, cp, 2);
word[i] = '%';
plpgsql_convert_ident(word, cp, 1);
/* Lookup the relation */
classOid = RelnameGetRelid(cp[0]);
@ -1807,16 +1768,12 @@ plpgsql_parse_wordrowtype(char *word)
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist", cp[0])));
/*
* Build and return the row type struct
*/
plpgsql_yylval.dtype = plpgsql_build_datatype(get_rel_type_id(classOid),
-1);
/* Build and return the row type struct */
dtype = plpgsql_build_datatype(get_rel_type_id(classOid), -1);
pfree(cp[0]);
pfree(cp[1]);
return T_DTYPE;
return dtype;
}
/* ----------
@ -1824,12 +1781,12 @@ plpgsql_parse_wordrowtype(char *word)
* So word must be a namespace qualified table name.
* ----------
*/
int
plpgsql_parse_dblwordrowtype(char *word)
PLpgSQL_type *
plpgsql_parse_dblwordrowtype(const char *word)
{
PLpgSQL_type *dtype;
Oid classOid;
char *cp[3];
int i;
char *cp[2];
RangeVar *relvar;
MemoryContext oldCxt;
@ -1837,12 +1794,7 @@ plpgsql_parse_dblwordrowtype(char *word)
oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
/* Do case conversion and word separation */
/* We convert %rowtype to .rowtype momentarily to keep converter happy */
i = strlen(word) - 8;
Assert(word[i] == '%');
word[i] = '.';
plpgsql_convert_ident(word, cp, 3);
word[i] = '%';
plpgsql_convert_ident(word, cp, 2);
/* Lookup the relation */
relvar = makeRangeVar(cp[0], cp[1], -1);
@ -1852,12 +1804,12 @@ plpgsql_parse_dblwordrowtype(char *word)
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist", cp[0], cp[1])));
/* Build and return the row type struct */
plpgsql_yylval.dtype = plpgsql_build_datatype(get_rel_type_id(classOid),
-1);
MemoryContextSwitchTo(oldCxt);
return T_DTYPE;
/* Build and return the row type struct */
dtype = plpgsql_build_datatype(get_rel_type_id(classOid), -1);
return dtype;
}
/*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.84 2009/11/06 18:37:54 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.85 2009/11/07 00:52:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -35,7 +35,6 @@
* ----------
*/
static PLpgSQL_nsitem *ns_top = NULL;
static bool ns_localmode = false;
/* ----------
@ -46,32 +45,6 @@ void
plpgsql_ns_init(void)
{
ns_top = NULL;
ns_localmode = false;
}
/* ----------
* plpgsql_ns_setlocal Tell plpgsql_ns_lookup whether to
* look into the current level only.
*
* This is a crock, but in the current design we need it because scan.l
* initiates name lookup, and the scanner does not know whether we are
* examining a name being declared in a DECLARE section. For that case
* we only want to know if there is a conflicting name earlier in the
* same DECLARE section. So the grammar must temporarily set local mode
* before scanning decl_varnames. This should eventually go away in favor
* of a localmode argument to plpgsql_ns_lookup, or perhaps some less
* indirect method of dealing with duplicate namespace entries.
* ----------
*/
bool
plpgsql_ns_setlocal(bool flag)
{
bool oldstate;
oldstate = ns_localmode;
ns_localmode = flag;
return oldstate;
}
@ -140,6 +113,8 @@ plpgsql_ns_additem(int itemtype, int itemno, const char *name)
*
* Note that this only searches for variables, not labels.
*
* If localmode is TRUE, only the topmost block level is searched.
*
* name1 must be non-NULL. Pass NULL for name2 and/or name3 if parsing a name
* with fewer than three components.
*
@ -154,7 +129,7 @@ plpgsql_ns_additem(int itemtype, int itemno, const char *name)
* ----------
*/
PLpgSQL_nsitem *
plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur,
plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur, bool localmode,
const char *name1, const char *name2, const char *name3,
int *names_used)
{
@ -201,7 +176,7 @@ plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur,
}
}
if (ns_localmode)
if (localmode)
break; /* do not look into upper levels */
ns_cur = nsitem->prev;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.120 2009/11/06 18:37:54 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.121 2009/11/07 00:52:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -773,6 +773,7 @@ typedef struct
**********************************************************************/
extern bool plpgsql_DumpExecTree;
extern bool plpgsql_LookupIdentifiers;
extern bool plpgsql_SpaceScanned;
extern int plpgsql_nDatums;
extern PLpgSQL_datum **plpgsql_Datums;
@ -807,11 +808,11 @@ extern void plpgsql_parser_setup(struct ParseState *pstate,
extern int plpgsql_parse_word(const char *word);
extern int plpgsql_parse_dblword(const char *word);
extern int plpgsql_parse_tripword(const char *word);
extern int plpgsql_parse_wordtype(char *word);
extern int plpgsql_parse_dblwordtype(char *word);
extern int plpgsql_parse_tripwordtype(char *word);
extern int plpgsql_parse_wordrowtype(char *word);
extern int plpgsql_parse_dblwordrowtype(char *word);
extern PLpgSQL_type *plpgsql_parse_wordtype(const char *word);
extern PLpgSQL_type *plpgsql_parse_dblwordtype(const char *word);
extern PLpgSQL_type *plpgsql_parse_tripwordtype(const char *word);
extern PLpgSQL_type *plpgsql_parse_wordrowtype(const char *word);
extern PLpgSQL_type *plpgsql_parse_dblwordrowtype(const char *word);
extern PLpgSQL_type *plpgsql_parse_datatype(const char *string);
extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod);
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
@ -857,12 +858,11 @@ extern Oid exec_get_rec_fieldtype(PLpgSQL_rec *rec, const char *fieldname,
* ----------
*/
extern void plpgsql_ns_init(void);
extern bool plpgsql_ns_setlocal(bool flag);
extern void plpgsql_ns_push(const char *label);
extern void plpgsql_ns_pop(void);
extern PLpgSQL_nsitem *plpgsql_ns_top(void);
extern void plpgsql_ns_additem(int itemtype, int itemno, const char *name);
extern PLpgSQL_nsitem *plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur,
extern PLpgSQL_nsitem *plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur, bool localmode,
const char *name1, const char *name2,
const char *name3, int *names_used);
extern PLpgSQL_nsitem *plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur,

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.73 2009/11/05 16:58:36 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.74 2009/11/07 00:52:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -43,6 +43,7 @@ static int cur_line_num;
static int xcdepth = 0; /* depth of nesting in slash-star comments */
static char *dolqstart; /* current $foo$ quote start string */
bool plpgsql_LookupIdentifiers = true;
bool plpgsql_SpaceScanned = false;
%}
@ -209,52 +210,28 @@ dump { return O_DUMP; }
*/
{identifier} {
plpgsql_error_lineno = plpgsql_scanner_lineno();
if (!plpgsql_LookupIdentifiers) return T_WORD;
return plpgsql_parse_word(yytext); }
{identifier}{space}*\.{space}*{identifier} {
plpgsql_error_lineno = plpgsql_scanner_lineno();
if (!plpgsql_LookupIdentifiers) return T_DBLWORD;
return plpgsql_parse_dblword(yytext); }
{identifier}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier} {
plpgsql_error_lineno = plpgsql_scanner_lineno();
if (!plpgsql_LookupIdentifiers) return T_TRIPWORD;
return plpgsql_parse_tripword(yytext); }
{identifier}{space}*%TYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_wordtype(yytext); }
{identifier}{space}*\.{space}*{identifier}{space}*%TYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_dblwordtype(yytext); }
{identifier}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}{space}*%TYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_tripwordtype(yytext); }
{identifier}{space}*%ROWTYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_wordrowtype(yytext); }
{identifier}{space}*\.{space}*{identifier}{space}*%ROWTYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_dblwordrowtype(yytext); }
{param} {
plpgsql_error_lineno = plpgsql_scanner_lineno();
if (!plpgsql_LookupIdentifiers) return T_WORD;
return plpgsql_parse_word(yytext); }
{param}{space}*\.{space}*{identifier} {
plpgsql_error_lineno = plpgsql_scanner_lineno();
if (!plpgsql_LookupIdentifiers) return T_DBLWORD;
return plpgsql_parse_dblword(yytext); }
{param}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier} {
plpgsql_error_lineno = plpgsql_scanner_lineno();
if (!plpgsql_LookupIdentifiers) return T_TRIPWORD;
return plpgsql_parse_tripword(yytext); }
{param}{space}*%TYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_wordtype(yytext); }
{param}{space}*\.{space}*{identifier}{space}*%TYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_dblwordtype(yytext); }
{param}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}{space}*%TYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_tripwordtype(yytext); }
{param}{space}*%ROWTYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_wordrowtype(yytext); }
{param}{space}*\.{space}*{identifier}{space}*%ROWTYPE {
plpgsql_error_lineno = plpgsql_scanner_lineno();
return plpgsql_parse_dblwordrowtype(yytext); }
{digit}+ { return T_NUMBER; }
@ -527,6 +504,8 @@ plpgsql_scanner_init(const char *str)
cur_line_start++;
BEGIN(INITIAL);
plpgsql_LookupIdentifiers = true;
plpgsql_SpaceScanned = false;
}
/*