mirror of https://github.com/postgres/postgres
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2509 lines
57 KiB
2509 lines
57 KiB
%{
|
|
/*-------------------------------------------------------------------------
|
|
*
|
|
* gram.y - Parser for the PL/pgSQL
|
|
* procedural language
|
|
*
|
|
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.105 2007/07/25 04:19:08 neilc Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "plpgsql.h"
|
|
|
|
#include "parser/parser.h"
|
|
|
|
|
|
static PLpgSQL_expr *read_sql_construct(int until,
|
|
int until2,
|
|
const char *expected,
|
|
const char *sqlstart,
|
|
bool isexpression,
|
|
bool valid_sql,
|
|
int *endtoken);
|
|
static PLpgSQL_expr *read_sql_stmt(const char *sqlstart);
|
|
static PLpgSQL_type *read_datatype(int tok);
|
|
static PLpgSQL_stmt *make_execsql_stmt(const char *sqlstart, int lineno);
|
|
static PLpgSQL_stmt_fetch *read_fetch_direction(void);
|
|
static PLpgSQL_stmt *make_return_stmt(int lineno);
|
|
static PLpgSQL_stmt *make_return_next_stmt(int lineno);
|
|
static PLpgSQL_stmt *make_return_query_stmt(int lineno);
|
|
static void check_assignable(PLpgSQL_datum *datum);
|
|
static void read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row,
|
|
bool *strict);
|
|
static PLpgSQL_row *read_into_scalar_list(const char *initial_name,
|
|
PLpgSQL_datum *initial_datum);
|
|
static PLpgSQL_row *make_scalar_list1(const char *initial_name,
|
|
PLpgSQL_datum *initial_datum,
|
|
int lineno);
|
|
static void check_sql_expr(const char *stmt);
|
|
static void plpgsql_sql_error_callback(void *arg);
|
|
static void check_labels(const char *start_label,
|
|
const char *end_label);
|
|
|
|
%}
|
|
|
|
%name-prefix="plpgsql_yy"
|
|
|
|
%union {
|
|
int32 ival;
|
|
bool boolean;
|
|
char *str;
|
|
struct
|
|
{
|
|
char *name;
|
|
int lineno;
|
|
} varname;
|
|
struct
|
|
{
|
|
char *name;
|
|
int lineno;
|
|
PLpgSQL_datum *scalar;
|
|
PLpgSQL_rec *rec;
|
|
PLpgSQL_row *row;
|
|
} forvariable;
|
|
struct
|
|
{
|
|
char *label;
|
|
int n_initvars;
|
|
int *initvarnos;
|
|
} declhdr;
|
|
struct
|
|
{
|
|
char *end_label;
|
|
List *stmts;
|
|
} loop_body;
|
|
List *list;
|
|
PLpgSQL_type *dtype;
|
|
PLpgSQL_datum *scalar; /* a VAR, RECFIELD, or TRIGARG */
|
|
PLpgSQL_variable *variable; /* a VAR, REC, or ROW */
|
|
PLpgSQL_var *var;
|
|
PLpgSQL_row *row;
|
|
PLpgSQL_rec *rec;
|
|
PLpgSQL_expr *expr;
|
|
PLpgSQL_stmt *stmt;
|
|
PLpgSQL_stmt_block *program;
|
|
PLpgSQL_condition *condition;
|
|
PLpgSQL_exception *exception;
|
|
PLpgSQL_exception_block *exception_block;
|
|
PLpgSQL_nsitem *nsitem;
|
|
PLpgSQL_diag_item *diagitem;
|
|
PLpgSQL_stmt_fetch *fetch;
|
|
}
|
|
|
|
%type <declhdr> decl_sect
|
|
%type <varname> decl_varname
|
|
%type <str> decl_renname
|
|
%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 <list> decl_cursor_arglist
|
|
%type <nsitem> decl_aliasitem
|
|
%type <str> decl_stmts decl_stmt
|
|
|
|
%type <expr> expr_until_semi expr_until_rightbracket
|
|
%type <expr> expr_until_then expr_until_loop
|
|
%type <expr> opt_exitcond
|
|
|
|
%type <ival> assign_var
|
|
%type <var> cursor_variable
|
|
%type <variable> decl_cursor_arg
|
|
%type <forvariable> for_variable
|
|
%type <stmt> for_control
|
|
|
|
%type <str> opt_lblname opt_block_label opt_label
|
|
%type <str> execsql_start
|
|
|
|
%type <list> proc_sect proc_stmts stmt_else
|
|
%type <loop_body> loop_body
|
|
%type <stmt> proc_stmt pl_block
|
|
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
|
|
%type <stmt> stmt_return stmt_raise stmt_execsql stmt_execsql_insert
|
|
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag
|
|
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
|
|
|
|
%type <list> proc_exceptions
|
|
%type <exception_block> exception_sect
|
|
%type <exception> proc_exception
|
|
%type <condition> proc_conditions
|
|
|
|
|
|
%type <ival> raise_level
|
|
%type <str> raise_msg
|
|
|
|
%type <list> getdiag_list
|
|
%type <diagitem> getdiag_list_item
|
|
%type <ival> getdiag_kind getdiag_target
|
|
|
|
%type <ival> opt_scrollable
|
|
%type <fetch> opt_fetch_direction
|
|
|
|
%type <ival> lno
|
|
|
|
/*
|
|
* Keyword tokens
|
|
*/
|
|
%token K_ALIAS
|
|
%token K_ASSIGN
|
|
%token K_BEGIN
|
|
%token K_BY
|
|
%token K_CLOSE
|
|
%token K_CONSTANT
|
|
%token K_CONTINUE
|
|
%token K_CURSOR
|
|
%token K_DEBUG
|
|
%token K_DECLARE
|
|
%token K_DEFAULT
|
|
%token K_DIAGNOSTICS
|
|
%token K_DOTDOT
|
|
%token K_ELSE
|
|
%token K_ELSIF
|
|
%token K_END
|
|
%token K_EXCEPTION
|
|
%token K_EXECUTE
|
|
%token K_EXIT
|
|
%token K_FOR
|
|
%token K_FETCH
|
|
%token K_FROM
|
|
%token K_GET
|
|
%token K_IF
|
|
%token K_IN
|
|
%token K_INFO
|
|
%token K_INSERT
|
|
%token K_INTO
|
|
%token K_IS
|
|
%token K_LOG
|
|
%token K_LOOP
|
|
%token K_MOVE
|
|
%token K_NEXT
|
|
%token K_NOSCROLL
|
|
%token K_NOT
|
|
%token K_NOTICE
|
|
%token K_NULL
|
|
%token K_OPEN
|
|
%token K_OR
|
|
%token K_QUERY
|
|
%token K_PERFORM
|
|
%token K_ROW_COUNT
|
|
%token K_RAISE
|
|
%token K_RENAME
|
|
%token K_RESULT_OID
|
|
%token K_RETURN
|
|
%token K_REVERSE
|
|
%token K_SCROLL
|
|
%token K_STRICT
|
|
%token K_THEN
|
|
%token K_TO
|
|
%token K_TYPE
|
|
%token K_WARNING
|
|
%token K_WHEN
|
|
%token K_WHILE
|
|
|
|
/*
|
|
* Other tokens
|
|
*/
|
|
%token T_FUNCTION
|
|
%token T_TRIGGER
|
|
%token T_STRING
|
|
%token T_NUMBER
|
|
%token T_SCALAR /* a VAR, RECFIELD, or TRIGARG */
|
|
%token T_ROW
|
|
%token T_RECORD
|
|
%token T_DTYPE
|
|
%token T_LABEL
|
|
%token T_WORD
|
|
%token T_ERROR
|
|
|
|
%token O_OPTION
|
|
%token O_DUMP
|
|
|
|
%%
|
|
|
|
pl_function : T_FUNCTION comp_optsect pl_block opt_semi
|
|
{
|
|
yylval.program = (PLpgSQL_stmt_block *)$3;
|
|
}
|
|
| T_TRIGGER comp_optsect pl_block opt_semi
|
|
{
|
|
yylval.program = (PLpgSQL_stmt_block *)$3;
|
|
}
|
|
;
|
|
|
|
comp_optsect :
|
|
| comp_options
|
|
;
|
|
|
|
comp_options : comp_options comp_option
|
|
| comp_option
|
|
;
|
|
|
|
comp_option : O_OPTION O_DUMP
|
|
{
|
|
plpgsql_DumpExecTree = true;
|
|
}
|
|
;
|
|
|
|
opt_semi :
|
|
| ';'
|
|
;
|
|
|
|
pl_block : decl_sect K_BEGIN lno proc_sect exception_sect K_END opt_label
|
|
{
|
|
PLpgSQL_stmt_block *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_block));
|
|
|
|
new->cmd_type = PLPGSQL_STMT_BLOCK;
|
|
new->lineno = $3;
|
|
new->label = $1.label;
|
|
new->n_initvars = $1.n_initvars;
|
|
new->initvarnos = $1.initvarnos;
|
|
new->body = $4;
|
|
new->exceptions = $5;
|
|
|
|
check_labels($1.label, $7);
|
|
plpgsql_ns_pop();
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
|
|
decl_sect : opt_block_label
|
|
{
|
|
plpgsql_ns_setlocal(false);
|
|
$$.label = $1;
|
|
$$.n_initvars = 0;
|
|
$$.initvarnos = NULL;
|
|
}
|
|
| opt_block_label decl_start
|
|
{
|
|
plpgsql_ns_setlocal(false);
|
|
$$.label = $1;
|
|
$$.n_initvars = 0;
|
|
$$.initvarnos = NULL;
|
|
}
|
|
| opt_block_label decl_start decl_stmts
|
|
{
|
|
plpgsql_ns_setlocal(false);
|
|
if ($3 != NULL)
|
|
$$.label = $3;
|
|
else
|
|
$$.label = $1;
|
|
/* Remember variables declared in decl_stmts */
|
|
$$.n_initvars = plpgsql_add_initdatums(&($$.initvarnos));
|
|
}
|
|
;
|
|
|
|
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);
|
|
}
|
|
;
|
|
|
|
decl_stmts : decl_stmts decl_stmt
|
|
{ $$ = $2; }
|
|
| decl_stmt
|
|
{ $$ = $1; }
|
|
;
|
|
|
|
decl_stmt : '<' '<' opt_lblname '>' '>'
|
|
{ $$ = $3; }
|
|
| K_DECLARE
|
|
{ $$ = NULL; }
|
|
| decl_statement
|
|
{ $$ = NULL; }
|
|
;
|
|
|
|
decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval
|
|
{
|
|
PLpgSQL_variable *var;
|
|
|
|
var = plpgsql_build_variable($1.name, $1.lineno,
|
|
$3, true);
|
|
if ($2)
|
|
{
|
|
if (var->dtype == PLPGSQL_DTYPE_VAR)
|
|
((PLpgSQL_var *) var)->isconst = $2;
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("row or record variable cannot be CONSTANT")));
|
|
}
|
|
if ($4)
|
|
{
|
|
if (var->dtype == PLPGSQL_DTYPE_VAR)
|
|
((PLpgSQL_var *) var)->notnull = $4;
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("row or record variable cannot be NOT NULL")));
|
|
}
|
|
if ($5 != NULL)
|
|
{
|
|
if (var->dtype == PLPGSQL_DTYPE_VAR)
|
|
((PLpgSQL_var *) var)->default_val = $5;
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("default value for row or record variable is not supported")));
|
|
}
|
|
}
|
|
| decl_varname K_ALIAS K_FOR decl_aliasitem ';'
|
|
{
|
|
plpgsql_ns_additem($4->itemtype,
|
|
$4->itemno, $1.name);
|
|
}
|
|
| K_RENAME decl_renname K_TO decl_renname ';'
|
|
{
|
|
plpgsql_ns_rename($2, $4);
|
|
}
|
|
| decl_varname opt_scrollable K_CURSOR
|
|
{ plpgsql_ns_push($1.name); }
|
|
decl_cursor_args decl_is_for decl_cursor_query
|
|
{
|
|
PLpgSQL_var *new;
|
|
PLpgSQL_expr *curname_def;
|
|
char buf[1024];
|
|
char *cp1;
|
|
char *cp2;
|
|
|
|
/* pop local namespace for cursor args */
|
|
plpgsql_ns_pop();
|
|
|
|
new = (PLpgSQL_var *)
|
|
plpgsql_build_variable($1.name, $1.lineno,
|
|
plpgsql_build_datatype(REFCURSOROID,
|
|
-1),
|
|
true);
|
|
|
|
curname_def = palloc0(sizeof(PLpgSQL_expr));
|
|
|
|
curname_def->dtype = PLPGSQL_DTYPE_EXPR;
|
|
strcpy(buf, "SELECT ");
|
|
cp1 = new->refname;
|
|
cp2 = buf + strlen(buf);
|
|
/*
|
|
* Don't trust standard_conforming_strings here;
|
|
* it might change before we use the string.
|
|
*/
|
|
if (strchr(cp1, '\\') != NULL)
|
|
*cp2++ = ESCAPE_STRING_SYNTAX;
|
|
*cp2++ = '\'';
|
|
while (*cp1)
|
|
{
|
|
if (SQL_STR_DOUBLE(*cp1, true))
|
|
*cp2++ = *cp1;
|
|
*cp2++ = *cp1++;
|
|
}
|
|
strcpy(cp2, "'::refcursor");
|
|
curname_def->query = pstrdup(buf);
|
|
new->default_val = curname_def;
|
|
|
|
new->cursor_explicit_expr = $7;
|
|
if ($5 == NULL)
|
|
new->cursor_explicit_argrow = -1;
|
|
else
|
|
new->cursor_explicit_argrow = $5->rowno;
|
|
new->cursor_options = CURSOR_OPT_FAST_PLAN | $2;
|
|
}
|
|
;
|
|
|
|
opt_scrollable :
|
|
{
|
|
$$ = 0;
|
|
}
|
|
| K_NOSCROLL
|
|
{
|
|
$$ = CURSOR_OPT_NO_SCROLL;
|
|
}
|
|
| K_SCROLL
|
|
{
|
|
$$ = CURSOR_OPT_SCROLL;
|
|
}
|
|
;
|
|
|
|
decl_cursor_query :
|
|
{
|
|
PLpgSQL_expr *query;
|
|
|
|
plpgsql_ns_setlocal(false);
|
|
query = read_sql_stmt("");
|
|
plpgsql_ns_setlocal(true);
|
|
|
|
$$ = query;
|
|
}
|
|
;
|
|
|
|
decl_cursor_args :
|
|
{
|
|
$$ = NULL;
|
|
}
|
|
| '(' decl_cursor_arglist ')'
|
|
{
|
|
PLpgSQL_row *new;
|
|
int i;
|
|
ListCell *l;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_row));
|
|
new->dtype = PLPGSQL_DTYPE_ROW;
|
|
new->lineno = plpgsql_scanner_lineno();
|
|
new->rowtupdesc = NULL;
|
|
new->nfields = list_length($2);
|
|
new->fieldnames = palloc(new->nfields * sizeof(char *));
|
|
new->varnos = palloc(new->nfields * sizeof(int));
|
|
|
|
i = 0;
|
|
foreach (l, $2)
|
|
{
|
|
PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l);
|
|
new->fieldnames[i] = arg->refname;
|
|
new->varnos[i] = arg->dno;
|
|
i++;
|
|
}
|
|
list_free($2);
|
|
|
|
plpgsql_adddatum((PLpgSQL_datum *) new);
|
|
$$ = new;
|
|
}
|
|
;
|
|
|
|
decl_cursor_arglist : decl_cursor_arg
|
|
{
|
|
$$ = list_make1($1);
|
|
}
|
|
| decl_cursor_arglist ',' decl_cursor_arg
|
|
{
|
|
$$ = lappend($1, $3);
|
|
}
|
|
;
|
|
|
|
decl_cursor_arg : decl_varname decl_datatype
|
|
{
|
|
$$ = plpgsql_build_variable($1.name, $1.lineno,
|
|
$2, true);
|
|
}
|
|
;
|
|
|
|
decl_is_for : K_IS | /* Oracle */
|
|
K_FOR; /* ANSI */
|
|
|
|
decl_aliasitem : T_WORD
|
|
{
|
|
char *name;
|
|
PLpgSQL_nsitem *nsi;
|
|
|
|
plpgsql_convert_ident(yytext, &name, 1);
|
|
if (name[0] != '$')
|
|
yyerror("only positional parameters can be aliased");
|
|
|
|
plpgsql_ns_setlocal(false);
|
|
nsi = plpgsql_ns_lookup(name, NULL);
|
|
if (nsi == NULL)
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_PARAMETER),
|
|
errmsg("function has no parameter \"%s\"",
|
|
name)));
|
|
}
|
|
|
|
plpgsql_ns_setlocal(true);
|
|
|
|
pfree(name);
|
|
|
|
$$ = nsi;
|
|
}
|
|
;
|
|
|
|
decl_varname : T_WORD
|
|
{
|
|
char *name;
|
|
|
|
plpgsql_convert_ident(yytext, &name, 1);
|
|
$$.name = name;
|
|
$$.lineno = plpgsql_scanner_lineno();
|
|
}
|
|
;
|
|
|
|
decl_renname : T_WORD
|
|
{
|
|
char *name;
|
|
|
|
plpgsql_convert_ident(yytext, &name, 1);
|
|
/* the result must be palloc'd, see plpgsql_ns_rename */
|
|
$$ = name;
|
|
}
|
|
;
|
|
|
|
decl_const :
|
|
{ $$ = false; }
|
|
| K_CONSTANT
|
|
{ $$ = true; }
|
|
;
|
|
|
|
decl_datatype :
|
|
{
|
|
/*
|
|
* If there's a lookahead token, read_datatype
|
|
* should consume it.
|
|
*/
|
|
$$ = read_datatype(yychar);
|
|
yyclearin;
|
|
}
|
|
;
|
|
|
|
decl_notnull :
|
|
{ $$ = false; }
|
|
| K_NOT K_NULL
|
|
{ $$ = true; }
|
|
;
|
|
|
|
decl_defval : ';'
|
|
{ $$ = NULL; }
|
|
| decl_defkey
|
|
{
|
|
plpgsql_ns_setlocal(false);
|
|
$$ = plpgsql_read_expression(';', ";");
|
|
plpgsql_ns_setlocal(true);
|
|
}
|
|
;
|
|
|
|
decl_defkey : K_ASSIGN
|
|
| K_DEFAULT
|
|
;
|
|
|
|
proc_sect :
|
|
{
|
|
$$ = NIL;
|
|
}
|
|
| proc_stmts
|
|
{ $$ = $1; }
|
|
;
|
|
|
|
proc_stmts : proc_stmts proc_stmt
|
|
{
|
|
if ($2 == NULL)
|
|
$$ = $1;
|
|
else
|
|
$$ = lappend($1, $2);
|
|
}
|
|
| proc_stmt
|
|
{
|
|
if ($1 == NULL)
|
|
$$ = NULL;
|
|
else
|
|
$$ = list_make1($1);
|
|
}
|
|
;
|
|
|
|
proc_stmt : pl_block ';'
|
|
{ $$ = $1; }
|
|
| stmt_assign
|
|
{ $$ = $1; }
|
|
| stmt_if
|
|
{ $$ = $1; }
|
|
| stmt_loop
|
|
{ $$ = $1; }
|
|
| stmt_while
|
|
{ $$ = $1; }
|
|
| stmt_for
|
|
{ $$ = $1; }
|
|
| stmt_exit
|
|
{ $$ = $1; }
|
|
| stmt_return
|
|
{ $$ = $1; }
|
|
| stmt_raise
|
|
{ $$ = $1; }
|
|
| stmt_execsql
|
|
{ $$ = $1; }
|
|
| stmt_execsql_insert
|
|
{ $$ = $1; }
|
|
| stmt_dynexecute
|
|
{ $$ = $1; }
|
|
| stmt_perform
|
|
{ $$ = $1; }
|
|
| stmt_getdiag
|
|
{ $$ = $1; }
|
|
| stmt_open
|
|
{ $$ = $1; }
|
|
| stmt_fetch
|
|
{ $$ = $1; }
|
|
| stmt_move
|
|
{ $$ = $1; }
|
|
| stmt_close
|
|
{ $$ = $1; }
|
|
| stmt_null
|
|
{ $$ = $1; }
|
|
;
|
|
|
|
stmt_perform : K_PERFORM lno expr_until_semi
|
|
{
|
|
PLpgSQL_stmt_perform *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_perform));
|
|
new->cmd_type = PLPGSQL_STMT_PERFORM;
|
|
new->lineno = $2;
|
|
new->expr = $3;
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
stmt_assign : assign_var lno K_ASSIGN expr_until_semi
|
|
{
|
|
PLpgSQL_stmt_assign *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_assign));
|
|
new->cmd_type = PLPGSQL_STMT_ASSIGN;
|
|
new->lineno = $2;
|
|
new->varno = $1;
|
|
new->expr = $4;
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
stmt_getdiag : K_GET K_DIAGNOSTICS lno getdiag_list ';'
|
|
{
|
|
PLpgSQL_stmt_getdiag *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_getdiag));
|
|
new->cmd_type = PLPGSQL_STMT_GETDIAG;
|
|
new->lineno = $3;
|
|
new->diag_items = $4;
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
getdiag_list : getdiag_list ',' getdiag_list_item
|
|
{
|
|
$$ = lappend($1, $3);
|
|
}
|
|
| getdiag_list_item
|
|
{
|
|
$$ = list_make1($1);
|
|
}
|
|
;
|
|
|
|
getdiag_list_item : getdiag_target K_ASSIGN getdiag_kind
|
|
{
|
|
PLpgSQL_diag_item *new;
|
|
|
|
new = palloc(sizeof(PLpgSQL_diag_item));
|
|
new->target = $1;
|
|
new->kind = $3;
|
|
|
|
$$ = new;
|
|
}
|
|
;
|
|
|
|
getdiag_kind : K_ROW_COUNT
|
|
{
|
|
$$ = PLPGSQL_GETDIAG_ROW_COUNT;
|
|
}
|
|
| K_RESULT_OID
|
|
{
|
|
$$ = PLPGSQL_GETDIAG_RESULT_OID;
|
|
}
|
|
;
|
|
|
|
getdiag_target : T_SCALAR
|
|
{
|
|
check_assignable(yylval.scalar);
|
|
$$ = yylval.scalar->dno;
|
|
}
|
|
;
|
|
|
|
|
|
assign_var : T_SCALAR
|
|
{
|
|
check_assignable(yylval.scalar);
|
|
$$ = yylval.scalar->dno;
|
|
}
|
|
| T_ROW
|
|
{
|
|
check_assignable((PLpgSQL_datum *) yylval.row);
|
|
$$ = yylval.row->rowno;
|
|
}
|
|
| T_RECORD
|
|
{
|
|
check_assignable((PLpgSQL_datum *) yylval.rec);
|
|
$$ = yylval.rec->recno;
|
|
}
|
|
| assign_var '[' expr_until_rightbracket
|
|
{
|
|
PLpgSQL_arrayelem *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_arrayelem));
|
|
new->dtype = PLPGSQL_DTYPE_ARRAYELEM;
|
|
new->subscript = $3;
|
|
new->arrayparentno = $1;
|
|
|
|
plpgsql_adddatum((PLpgSQL_datum *)new);
|
|
|
|
$$ = new->dno;
|
|
}
|
|
;
|
|
|
|
stmt_if : K_IF lno expr_until_then proc_sect stmt_else K_END K_IF ';'
|
|
{
|
|
PLpgSQL_stmt_if *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_if));
|
|
new->cmd_type = PLPGSQL_STMT_IF;
|
|
new->lineno = $2;
|
|
new->cond = $3;
|
|
new->true_body = $4;
|
|
new->false_body = $5;
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
stmt_else :
|
|
{
|
|
$$ = NIL;
|
|
}
|
|
| K_ELSIF lno expr_until_then proc_sect stmt_else
|
|
{
|
|
/*
|
|
* Translate the structure: into:
|
|
*
|
|
* IF c1 THEN IF c1 THEN
|
|
* ... ...
|
|
* ELSIF c2 THEN ELSE
|
|
* IF c2 THEN
|
|
* ... ...
|
|
* ELSE ELSE
|
|
* ... ...
|
|
* END IF END IF
|
|
* END IF
|
|
*/
|
|
PLpgSQL_stmt_if *new_if;
|
|
|
|
/* first create a new if-statement */
|
|
new_if = palloc0(sizeof(PLpgSQL_stmt_if));
|
|
new_if->cmd_type = PLPGSQL_STMT_IF;
|
|
new_if->lineno = $2;
|
|
new_if->cond = $3;
|
|
new_if->true_body = $4;
|
|
new_if->false_body = $5;
|
|
|
|
/* wrap the if-statement in a "container" list */
|
|
$$ = list_make1(new_if);
|
|
}
|
|
|
|
| K_ELSE proc_sect
|
|
{
|
|
$$ = $2;
|
|
}
|
|
;
|
|
|
|
stmt_loop : opt_block_label K_LOOP lno loop_body
|
|
{
|
|
PLpgSQL_stmt_loop *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_loop));
|
|
new->cmd_type = PLPGSQL_STMT_LOOP;
|
|
new->lineno = $3;
|
|
new->label = $1;
|
|
new->body = $4.stmts;
|
|
|
|
check_labels($1, $4.end_label);
|
|
plpgsql_ns_pop();
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
stmt_while : opt_block_label K_WHILE lno expr_until_loop loop_body
|
|
{
|
|
PLpgSQL_stmt_while *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_while));
|
|
new->cmd_type = PLPGSQL_STMT_WHILE;
|
|
new->lineno = $3;
|
|
new->label = $1;
|
|
new->cond = $4;
|
|
new->body = $5.stmts;
|
|
|
|
check_labels($1, $5.end_label);
|
|
plpgsql_ns_pop();
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
stmt_for : opt_block_label K_FOR for_control loop_body
|
|
{
|
|
/* This runs after we've scanned the loop body */
|
|
if ($3->cmd_type == PLPGSQL_STMT_FORI)
|
|
{
|
|
PLpgSQL_stmt_fori *new;
|
|
|
|
new = (PLpgSQL_stmt_fori *) $3;
|
|
new->label = $1;
|
|
new->body = $4.stmts;
|
|
$$ = (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.stmts;
|
|
$$ = (PLpgSQL_stmt *) new;
|
|
}
|
|
else
|
|
{
|
|
PLpgSQL_stmt_dynfors *new;
|
|
|
|
Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
|
|
new = (PLpgSQL_stmt_dynfors *) $3;
|
|
new->label = $1;
|
|
new->body = $4.stmts;
|
|
$$ = (PLpgSQL_stmt *) new;
|
|
}
|
|
|
|
check_labels($1, $4.end_label);
|
|
/* close namespace started in opt_block_label */
|
|
plpgsql_ns_pop();
|
|
}
|
|
;
|
|
|
|
for_control :
|
|
lno for_variable K_IN
|
|
{
|
|
int tok = yylex();
|
|
|
|
/* Simple case: EXECUTE is a dynamic FOR loop */
|
|
if (tok == K_EXECUTE)
|
|
{
|
|
PLpgSQL_stmt_dynfors *new;
|
|
PLpgSQL_expr *expr;
|
|
|
|
expr = plpgsql_read_expression(K_LOOP, "LOOP");
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_dynfors));
|
|
new->cmd_type = PLPGSQL_STMT_DYNFORS;
|
|
new->lineno = $1;
|
|
if ($2.rec)
|
|
{
|
|
new->rec = $2.rec;
|
|
check_assignable((PLpgSQL_datum *) new->rec);
|
|
}
|
|
else if ($2.row)
|
|
{
|
|
new->row = $2.row;
|
|
check_assignable((PLpgSQL_datum *) new->row);
|
|
}
|
|
else if ($2.scalar)
|
|
{
|
|
/* convert single scalar to list */
|
|
new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
|
|
/* no need for check_assignable */
|
|
}
|
|
else
|
|
{
|
|
plpgsql_error_lineno = $2.lineno;
|
|
yyerror("loop variable of loop over rows must be a record or row variable or list of scalar variables");
|
|
}
|
|
new->query = expr;
|
|
|
|
$$ = (PLpgSQL_stmt *) new;
|
|
}
|
|
else
|
|
{
|
|
PLpgSQL_expr *expr1;
|
|
bool reverse = false;
|
|
|
|
/*
|
|
* We have to distinguish between two
|
|
* alternatives: FOR var IN a .. b and FOR
|
|
* var IN query. Unfortunately this is
|
|
* tricky, since the query in the second
|
|
* form needn't start with a SELECT
|
|
* keyword. We use the ugly hack of
|
|
* looking for two periods after the first
|
|
* token. We also check for the REVERSE
|
|
* keyword, which means it must be an
|
|
* integer loop.
|
|
*/
|
|
if (tok == K_REVERSE)
|
|
reverse = true;
|
|
else
|
|
plpgsql_push_back_token(tok);
|
|
|
|
/*
|
|
* Read tokens until we see either a ".."
|
|
* or a LOOP. The text we read may not
|
|
* necessarily be a well-formed SQL
|
|
* statement, so we need to invoke
|
|
* read_sql_construct directly.
|
|
*/
|
|
expr1 = read_sql_construct(K_DOTDOT,
|
|
K_LOOP,
|
|
"LOOP",
|
|
"SELECT ",
|
|
true,
|
|
false,
|
|
&tok);
|
|
|
|
if (tok == K_DOTDOT)
|
|
{
|
|
/* Saw "..", so it must be an integer loop */
|
|
PLpgSQL_expr *expr2;
|
|
PLpgSQL_expr *expr_by;
|
|
PLpgSQL_var *fvar;
|
|
PLpgSQL_stmt_fori *new;
|
|
char *varname;
|
|
|
|
/* Check first expression is well-formed */
|
|
check_sql_expr(expr1->query);
|
|
|
|
/* Read and check the second one */
|
|
expr2 = read_sql_construct(K_LOOP,
|
|
K_BY,
|
|
"LOOP",
|
|
"SELECT ",
|
|
true,
|
|
true,
|
|
&tok);
|
|
|
|
/* Get the BY clause if any */
|
|
if (tok == K_BY)
|
|
expr_by = plpgsql_read_expression(K_LOOP, "LOOP");
|
|
else
|
|
expr_by = NULL;
|
|
|
|
/* Should have had a single variable name */
|
|
plpgsql_error_lineno = $2.lineno;
|
|
if ($2.scalar && $2.row)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("integer FOR loop must have just one target variable")));
|
|
|
|
/* create loop's private variable */
|
|
plpgsql_convert_ident($2.name, &varname, 1);
|
|
fvar = (PLpgSQL_var *)
|
|
plpgsql_build_variable(varname,
|
|
$2.lineno,
|
|
plpgsql_build_datatype(INT4OID,
|
|
-1),
|
|
true);
|
|
|
|
new = palloc0(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;
|
|
new->step = expr_by;
|
|
|
|
$$ = (PLpgSQL_stmt *) new;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* No "..", so it must be a query loop. We've prefixed an
|
|
* extra SELECT to the query text, so we need to remove that
|
|
* before performing syntax checking.
|
|
*/
|
|
char *tmp_query;
|
|
PLpgSQL_stmt_fors *new;
|
|
|
|
if (reverse)
|
|
yyerror("cannot specify REVERSE in query FOR loop");
|
|
|
|
Assert(strncmp(expr1->query, "SELECT ", 7) == 0);
|
|
tmp_query = pstrdup(expr1->query + 7);
|
|
pfree(expr1->query);
|
|
expr1->query = tmp_query;
|
|
|
|
check_sql_expr(expr1->query);
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_fors));
|
|
new->cmd_type = PLPGSQL_STMT_FORS;
|
|
new->lineno = $1;
|
|
if ($2.rec)
|
|
{
|
|
new->rec = $2.rec;
|
|
check_assignable((PLpgSQL_datum *) new->rec);
|
|
}
|
|
else if ($2.row)
|
|
{
|
|
new->row = $2.row;
|
|
check_assignable((PLpgSQL_datum *) new->row);
|
|
}
|
|
else if ($2.scalar)
|
|
{
|
|
/* convert single scalar to list */
|
|
new->row = make_scalar_list1($2.name, $2.scalar, $2.lineno);
|
|
/* no need for check_assignable */
|
|
}
|
|
else
|
|
{
|
|
plpgsql_error_lineno = $2.lineno;
|
|
yyerror("loop variable of loop over rows must be a record or row variable or list of scalar variables");
|
|
}
|
|
|
|
new->query = expr1;
|
|
$$ = (PLpgSQL_stmt *) new;
|
|
}
|
|
}
|
|
}
|
|
;
|
|
|
|
/*
|
|
* Processing the for_variable is tricky because we don't yet know if the
|
|
* FOR is an integer FOR loop or a loop over query results. In the former
|
|
* case, the variable is just a name that we must instantiate as a loop
|
|
* local variable, regardless of any other definition it might have.
|
|
* Therefore, we always save the actual identifier into $$.name where it
|
|
* can be used for that case. We also save the outer-variable definition,
|
|
* if any, because that's what we need for the loop-over-query case. Note
|
|
* that we must NOT apply check_assignable() or any other semantic check
|
|
* until we know what's what.
|
|
*
|
|
* However, if we see a comma-separated list of names, we know that it
|
|
* can't be an integer FOR loop and so it's OK to check the variables
|
|
* immediately. In particular, for T_WORD followed by comma, we should
|
|
* complain that the name is not known rather than say it's a syntax error.
|
|
* Note that the non-error result of this case sets *both* $$.scalar and
|
|
* $$.row; see the for_control production.
|
|
*/
|
|
for_variable : T_SCALAR
|
|
{
|
|
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);
|
|
}
|
|
| T_WORD
|
|
{
|
|
int tok;
|
|
|
|
$$.name = pstrdup(yytext);
|
|
$$.lineno = plpgsql_scanner_lineno();
|
|
$$.scalar = NULL;
|
|
$$.rec = NULL;
|
|
$$.row = NULL;
|
|
/* check for comma-separated list */
|
|
tok = yylex();
|
|
plpgsql_push_back_token(tok);
|
|
if (tok == ',')
|
|
{
|
|
plpgsql_error_lineno = $$.lineno;
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("\"%s\" is not a scalar 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
|
|
{
|
|
PLpgSQL_stmt_exit *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_exit));
|
|
new->cmd_type = PLPGSQL_STMT_EXIT;
|
|
new->is_exit = $1;
|
|
new->lineno = $2;
|
|
new->label = $3;
|
|
new->cond = $4;
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
exit_type : K_EXIT
|
|
{
|
|
$$ = true;
|
|
}
|
|
| K_CONTINUE
|
|
{
|
|
$$ = false;
|
|
}
|
|
;
|
|
|
|
stmt_return : K_RETURN lno
|
|
{
|
|
int tok;
|
|
|
|
tok = yylex();
|
|
if (tok == K_NEXT)
|
|
{
|
|
$$ = make_return_next_stmt($2);
|
|
}
|
|
else if (tok == K_QUERY)
|
|
{
|
|
$$ = make_return_query_stmt($2);
|
|
}
|
|
else
|
|
{
|
|
plpgsql_push_back_token(tok);
|
|
$$ = make_return_stmt($2);
|
|
}
|
|
}
|
|
;
|
|
|
|
stmt_raise : K_RAISE lno raise_level raise_msg
|
|
{
|
|
PLpgSQL_stmt_raise *new;
|
|
int tok;
|
|
|
|
new = palloc(sizeof(PLpgSQL_stmt_raise));
|
|
|
|
new->cmd_type = PLPGSQL_STMT_RAISE;
|
|
new->lineno = $2;
|
|
new->elog_level = $3;
|
|
new->message = $4;
|
|
new->params = NIL;
|
|
|
|
tok = yylex();
|
|
|
|
/*
|
|
* We expect either a semi-colon, which
|
|
* indicates no parameters, or a comma that
|
|
* begins the list of parameter expressions
|
|
*/
|
|
if (tok != ',' && tok != ';')
|
|
yyerror("syntax error");
|
|
|
|
if (tok == ',')
|
|
{
|
|
PLpgSQL_expr *expr;
|
|
int term;
|
|
|
|
for (;;)
|
|
{
|
|
expr = read_sql_construct(',', ';', ", or ;",
|
|
"SELECT ",
|
|
true, true, &term);
|
|
new->params = lappend(new->params, expr);
|
|
if (term == ';')
|
|
break;
|
|
}
|
|
}
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
raise_msg : T_STRING
|
|
{
|
|
$$ = plpgsql_get_string_value();
|
|
}
|
|
;
|
|
|
|
raise_level : K_EXCEPTION
|
|
{
|
|
$$ = ERROR;
|
|
}
|
|
| K_WARNING
|
|
{
|
|
$$ = WARNING;
|
|
}
|
|
| K_NOTICE
|
|
{
|
|
$$ = NOTICE;
|
|
}
|
|
| K_INFO
|
|
{
|
|
$$ = INFO;
|
|
}
|
|
| K_LOG
|
|
{
|
|
$$ = LOG;
|
|
}
|
|
| K_DEBUG
|
|
{
|
|
$$ = DEBUG1;
|
|
}
|
|
;
|
|
|
|
loop_body : proc_sect K_END K_LOOP opt_label ';'
|
|
{
|
|
$$.stmts = $1;
|
|
$$.end_label = $4;
|
|
}
|
|
;
|
|
|
|
stmt_execsql : execsql_start lno
|
|
{
|
|
$$ = make_execsql_stmt($1, $2);
|
|
}
|
|
;
|
|
|
|
/* this matches any otherwise-unrecognized starting keyword */
|
|
execsql_start : T_WORD
|
|
{ $$ = pstrdup(yytext); }
|
|
| T_ERROR
|
|
{ $$ = pstrdup(yytext); }
|
|
;
|
|
|
|
stmt_execsql_insert : K_INSERT lno K_INTO
|
|
{
|
|
/*
|
|
* We have to special-case INSERT so that its INTO
|
|
* won't be treated as an INTO-variables clause.
|
|
*
|
|
* Fortunately, this is the only valid use of INTO
|
|
* in a pl/pgsql SQL command, and INTO is already
|
|
* a fully reserved word in the main grammar.
|
|
*/
|
|
$$ = make_execsql_stmt("INSERT INTO", $2);
|
|
}
|
|
;
|
|
|
|
stmt_dynexecute : K_EXECUTE lno
|
|
{
|
|
PLpgSQL_stmt_dynexecute *new;
|
|
PLpgSQL_expr *expr;
|
|
int endtoken;
|
|
|
|
expr = read_sql_construct(K_INTO, ';', "INTO|;",
|
|
"SELECT ",
|
|
true, true, &endtoken);
|
|
|
|
new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
|
|
new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
|
|
new->lineno = $2;
|
|
new->query = expr;
|
|
new->into = false;
|
|
new->strict = false;
|
|
new->rec = NULL;
|
|
new->row = NULL;
|
|
|
|
/* If we found "INTO", collect the argument */
|
|
if (endtoken == K_INTO)
|
|
{
|
|
new->into = true;
|
|
read_into_target(&new->rec, &new->row, &new->strict);
|
|
if (yylex() != ';')
|
|
yyerror("syntax error");
|
|
}
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
|
|
stmt_open : K_OPEN lno cursor_variable
|
|
{
|
|
PLpgSQL_stmt_open *new;
|
|
int tok;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_open));
|
|
new->cmd_type = PLPGSQL_STMT_OPEN;
|
|
new->lineno = $2;
|
|
new->curvar = $3->varno;
|
|
new->cursor_options = CURSOR_OPT_FAST_PLAN;
|
|
|
|
if ($3->cursor_explicit_expr == NULL)
|
|
{
|
|
/* be nice if we could use opt_scrollable here */
|
|
tok = yylex();
|
|
if (tok == K_NOSCROLL)
|
|
{
|
|
new->cursor_options |= CURSOR_OPT_NO_SCROLL;
|
|
tok = yylex();
|
|
}
|
|
else if (tok == K_SCROLL)
|
|
{
|
|
new->cursor_options |= CURSOR_OPT_SCROLL;
|
|
tok = yylex();
|
|
}
|
|
|
|
if (tok != K_FOR)
|
|
{
|
|
plpgsql_error_lineno = $2;
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("syntax error at \"%s\"",
|
|
yytext),
|
|
errdetail("Expected FOR to open a reference cursor.")));
|
|
}
|
|
|
|
tok = yylex();
|
|
if (tok == K_EXECUTE)
|
|
{
|
|
new->dynquery = read_sql_stmt("SELECT ");
|
|
}
|
|
else
|
|
{
|
|
plpgsql_push_back_token(tok);
|
|
new->query = read_sql_stmt("");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($3->cursor_explicit_argrow >= 0)
|
|
{
|
|
char *cp;
|
|
|
|
tok = yylex();
|
|
if (tok != '(')
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("cursor \"%s\" has arguments",
|
|
$3->refname)));
|
|
}
|
|
|
|
/*
|
|
* Push back the '(', else read_sql_stmt
|
|
* will complain about unbalanced parens.
|
|
*/
|
|
plpgsql_push_back_token(tok);
|
|
|
|
new->argquery = read_sql_stmt("SELECT ");
|
|
|
|
/*
|
|
* Now remove the leading and trailing parens,
|
|
* because we want "select 1, 2", not
|
|
* "select (1, 2)".
|
|
*/
|
|
cp = new->argquery->query;
|
|
|
|
if (strncmp(cp, "SELECT", 6) != 0)
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
/* internal error */
|
|
elog(ERROR, "expected \"SELECT (\", got \"%s\"",
|
|
new->argquery->query);
|
|
}
|
|
cp += 6;
|
|
while (*cp == ' ') /* could be more than 1 space here */
|
|
cp++;
|
|
if (*cp != '(')
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
/* internal error */
|
|
elog(ERROR, "expected \"SELECT (\", got \"%s\"",
|
|
new->argquery->query);
|
|
}
|
|
*cp = ' ';
|
|
|
|
cp += strlen(cp) - 1;
|
|
|
|
if (*cp != ')')
|
|
yyerror("expected \")\"");
|
|
*cp = '\0';
|
|
}
|
|
else
|
|
{
|
|
tok = yylex();
|
|
if (tok == '(')
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("cursor \"%s\" has no arguments",
|
|
$3->refname)));
|
|
}
|
|
|
|
if (tok != ';')
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("syntax error at \"%s\"",
|
|
yytext)));
|
|
}
|
|
}
|
|
}
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
stmt_fetch : K_FETCH lno opt_fetch_direction cursor_variable K_INTO
|
|
{
|
|
PLpgSQL_stmt_fetch *fetch = $3;
|
|
PLpgSQL_rec *rec;
|
|
PLpgSQL_row *row;
|
|
|
|
/* We have already parsed everything through the INTO keyword */
|
|
read_into_target(&rec, &row, NULL);
|
|
|
|
if (yylex() != ';')
|
|
yyerror("syntax error");
|
|
|
|
fetch->lineno = $2;
|
|
fetch->rec = rec;
|
|
fetch->row = row;
|
|
fetch->curvar = $4->varno;
|
|
fetch->is_move = false;
|
|
|
|
$$ = (PLpgSQL_stmt *)fetch;
|
|
}
|
|
;
|
|
|
|
stmt_move : K_MOVE lno opt_fetch_direction cursor_variable ';'
|
|
{
|
|
PLpgSQL_stmt_fetch *fetch = $3;
|
|
|
|
fetch->lineno = $2;
|
|
fetch->curvar = $4->varno;
|
|
fetch->is_move = true;
|
|
|
|
$$ = (PLpgSQL_stmt *)fetch;
|
|
}
|
|
;
|
|
|
|
opt_fetch_direction :
|
|
{
|
|
$$ = read_fetch_direction();
|
|
}
|
|
;
|
|
|
|
stmt_close : K_CLOSE lno cursor_variable ';'
|
|
{
|
|
PLpgSQL_stmt_close *new;
|
|
|
|
new = palloc(sizeof(PLpgSQL_stmt_close));
|
|
new->cmd_type = PLPGSQL_STMT_CLOSE;
|
|
new->lineno = $2;
|
|
new->curvar = $3->varno;
|
|
|
|
$$ = (PLpgSQL_stmt *)new;
|
|
}
|
|
;
|
|
|
|
stmt_null : K_NULL ';'
|
|
{
|
|
/* We do not bother building a node for NULL */
|
|
$$ = NULL;
|
|
}
|
|
;
|
|
|
|
cursor_variable : T_SCALAR
|
|
{
|
|
if (yylval.scalar->dtype != PLPGSQL_DTYPE_VAR)
|
|
yyerror("cursor variable must be a simple variable");
|
|
|
|
if (((PLpgSQL_var *) yylval.scalar)->datatype->typoid != REFCURSOROID)
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("\"%s\" must be of type cursor or refcursor",
|
|
((PLpgSQL_var *) yylval.scalar)->refname)));
|
|
}
|
|
$$ = (PLpgSQL_var *) yylval.scalar;
|
|
}
|
|
;
|
|
|
|
exception_sect :
|
|
{ $$ = NULL; }
|
|
| K_EXCEPTION lno
|
|
{
|
|
/*
|
|
* We use a mid-rule action to add these
|
|
* special variables to the namespace before
|
|
* parsing the WHEN clauses themselves.
|
|
*/
|
|
PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
|
|
PLpgSQL_variable *var;
|
|
|
|
var = plpgsql_build_variable("sqlstate", $2,
|
|
plpgsql_build_datatype(TEXTOID, -1),
|
|
true);
|
|
((PLpgSQL_var *) var)->isconst = true;
|
|
new->sqlstate_varno = var->dno;
|
|
|
|
var = plpgsql_build_variable("sqlerrm", $2,
|
|
plpgsql_build_datatype(TEXTOID, -1),
|
|
true);
|
|
((PLpgSQL_var *) var)->isconst = true;
|
|
new->sqlerrm_varno = var->dno;
|
|
|
|
$<exception_block>$ = new;
|
|
}
|
|
proc_exceptions
|
|
{
|
|
PLpgSQL_exception_block *new = $<exception_block>3;
|
|
new->exc_list = $4;
|
|
|
|
$$ = new;
|
|
}
|
|
;
|
|
|
|
proc_exceptions : proc_exceptions proc_exception
|
|
{
|
|
$$ = lappend($1, $2);
|
|
}
|
|
| proc_exception
|
|
{
|
|
$$ = list_make1($1);
|
|
}
|
|
;
|
|
|
|
proc_exception : K_WHEN lno proc_conditions K_THEN proc_sect
|
|
{
|
|
PLpgSQL_exception *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_exception));
|
|
new->lineno = $2;
|
|
new->conditions = $3;
|
|
new->action = $5;
|
|
|
|
$$ = new;
|
|
}
|
|
;
|
|
|
|
proc_conditions : proc_conditions K_OR opt_lblname
|
|
{
|
|
PLpgSQL_condition *old;
|
|
|
|
for (old = $1; old->next != NULL; old = old->next)
|
|
/* skip */ ;
|
|
old->next = plpgsql_parse_err_condition($3);
|
|
|
|
$$ = $1;
|
|
}
|
|
| opt_lblname
|
|
{
|
|
$$ = plpgsql_parse_err_condition($1);
|
|
}
|
|
;
|
|
|
|
expr_until_semi :
|
|
{ $$ = plpgsql_read_expression(';', ";"); }
|
|
;
|
|
|
|
expr_until_rightbracket :
|
|
{ $$ = plpgsql_read_expression(']', "]"); }
|
|
;
|
|
|
|
expr_until_then :
|
|
{ $$ = plpgsql_read_expression(K_THEN, "THEN"); }
|
|
;
|
|
|
|
expr_until_loop :
|
|
{ $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
|
|
;
|
|
|
|
opt_block_label :
|
|
{
|
|
plpgsql_ns_push(NULL);
|
|
$$ = NULL;
|
|
}
|
|
| '<' '<' opt_lblname '>' '>'
|
|
{
|
|
plpgsql_ns_push($3);
|
|
$$ = $3;
|
|
}
|
|
;
|
|
|
|
opt_label :
|
|
{
|
|
$$ = NULL;
|
|
}
|
|
| T_LABEL
|
|
{
|
|
char *label_name;
|
|
plpgsql_convert_ident(yytext, &label_name, 1);
|
|
$$ = label_name;
|
|
}
|
|
| T_WORD
|
|
{
|
|
/* just to give a better error than "syntax error" */
|
|
yyerror("no such label");
|
|
}
|
|
;
|
|
|
|
opt_exitcond : ';'
|
|
{ $$ = NULL; }
|
|
| K_WHEN expr_until_semi
|
|
{ $$ = $2; }
|
|
;
|
|
|
|
opt_lblname : T_WORD
|
|
{
|
|
char *name;
|
|
|
|
plpgsql_convert_ident(yytext, &name, 1);
|
|
$$ = name;
|
|
}
|
|
;
|
|
|
|
lno :
|
|
{
|
|
$$ = plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
}
|
|
;
|
|
|
|
%%
|
|
|
|
|
|
#define MAX_EXPR_PARAMS 1024
|
|
|
|
/*
|
|
* determine the expression parameter position to use for a plpgsql datum
|
|
*
|
|
* It is important that any given plpgsql datum map to just one parameter.
|
|
* We used to be sloppy and assign a separate parameter for each occurrence
|
|
* of a datum reference, but that fails for situations such as "select DATUM
|
|
* from ... group by DATUM".
|
|
*
|
|
* The params[] array must be of size MAX_EXPR_PARAMS.
|
|
*/
|
|
static int
|
|
assign_expr_param(int dno, int *params, int *nparams)
|
|
{
|
|
int i;
|
|
|
|
/* already have an instance of this dno? */
|
|
for (i = 0; i < *nparams; i++)
|
|
{
|
|
if (params[i] == dno)
|
|
return i+1;
|
|
}
|
|
/* check for array overflow */
|
|
if (*nparams >= MAX_EXPR_PARAMS)
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
errmsg("too many variables specified in SQL statement")));
|
|
}
|
|
/* add new parameter dno to array */
|
|
params[*nparams] = dno;
|
|
(*nparams)++;
|
|
return *nparams;
|
|
}
|
|
|
|
|
|
PLpgSQL_expr *
|
|
plpgsql_read_expression(int until, const char *expected)
|
|
{
|
|
return read_sql_construct(until, 0, expected, "SELECT ", true, true, NULL);
|
|
}
|
|
|
|
static PLpgSQL_expr *
|
|
read_sql_stmt(const char *sqlstart)
|
|
{
|
|
return read_sql_construct(';', 0, ";", sqlstart, false, true, 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
|
|
* sqlstart: text to prefix to the accumulated SQL text
|
|
* isexpression: whether to say we're reading an "expression" or a "statement"
|
|
* valid_sql: whether to check the syntax of the expr (prefixed with sqlstart)
|
|
* 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,
|
|
const char *sqlstart,
|
|
bool isexpression,
|
|
bool valid_sql,
|
|
int *endtoken)
|
|
{
|
|
int tok;
|
|
int lno;
|
|
PLpgSQL_dstring ds;
|
|
int parenlevel = 0;
|
|
int nparams = 0;
|
|
int params[MAX_EXPR_PARAMS];
|
|
char buf[32];
|
|
PLpgSQL_expr *expr;
|
|
|
|
lno = plpgsql_scanner_lineno();
|
|
plpgsql_dstring_init(&ds);
|
|
plpgsql_dstring_append(&ds, sqlstart);
|
|
|
|
for (;;)
|
|
{
|
|
tok = yylex();
|
|
if (tok == until && parenlevel == 0)
|
|
break;
|
|
if (tok == until2 && parenlevel == 0)
|
|
break;
|
|
if (tok == '(' || tok == '[')
|
|
parenlevel++;
|
|
else if (tok == ')' || tok == ']')
|
|
{
|
|
parenlevel--;
|
|
if (parenlevel < 0)
|
|
yyerror("mismatched parentheses");
|
|
}
|
|
/*
|
|
* 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 == ';')
|
|
{
|
|
if (parenlevel != 0)
|
|
yyerror("mismatched parentheses");
|
|
plpgsql_error_lineno = lno;
|
|
if (isexpression)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("missing \"%s\" at end of SQL expression",
|
|
expected)));
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("missing \"%s\" at end of SQL statement",
|
|
expected)));
|
|
}
|
|
|
|
if (plpgsql_SpaceScanned)
|
|
plpgsql_dstring_append(&ds, " ");
|
|
|
|
switch (tok)
|
|
{
|
|
case T_SCALAR:
|
|
snprintf(buf, sizeof(buf), " $%d ",
|
|
assign_expr_param(yylval.scalar->dno,
|
|
params, &nparams));
|
|
plpgsql_dstring_append(&ds, buf);
|
|
break;
|
|
|
|
case T_ROW:
|
|
snprintf(buf, sizeof(buf), " $%d ",
|
|
assign_expr_param(yylval.row->rowno,
|
|
params, &nparams));
|
|
plpgsql_dstring_append(&ds, buf);
|
|
break;
|
|
|
|
case T_RECORD:
|
|
snprintf(buf, sizeof(buf), " $%d ",
|
|
assign_expr_param(yylval.rec->recno,
|
|
params, &nparams));
|
|
plpgsql_dstring_append(&ds, buf);
|
|
break;
|
|
|
|
default:
|
|
plpgsql_dstring_append(&ds, yytext);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (endtoken)
|
|
*endtoken = tok;
|
|
|
|
expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
|
|
expr->dtype = PLPGSQL_DTYPE_EXPR;
|
|
expr->query = pstrdup(plpgsql_dstring_get(&ds));
|
|
expr->plan = NULL;
|
|
expr->nparams = nparams;
|
|
while(nparams-- > 0)
|
|
expr->params[nparams] = params[nparams];
|
|
plpgsql_dstring_free(&ds);
|
|
|
|
if (valid_sql)
|
|
check_sql_expr(expr->query);
|
|
|
|
return expr;
|
|
}
|
|
|
|
static PLpgSQL_type *
|
|
read_datatype(int tok)
|
|
{
|
|
int lno;
|
|
PLpgSQL_dstring ds;
|
|
char *type_name;
|
|
PLpgSQL_type *result;
|
|
bool needspace = false;
|
|
int parenlevel = 0;
|
|
|
|
lno = plpgsql_scanner_lineno();
|
|
|
|
/* Often there will be a lookahead token, but if not, get one */
|
|
if (tok == YYEMPTY)
|
|
tok = yylex();
|
|
|
|
if (tok == T_DTYPE)
|
|
{
|
|
/* lexer found word%TYPE and did its thing already */
|
|
return yylval.dtype;
|
|
}
|
|
|
|
plpgsql_dstring_init(&ds);
|
|
|
|
while (tok != ';')
|
|
{
|
|
if (tok == 0)
|
|
{
|
|
if (parenlevel != 0)
|
|
yyerror("mismatched parentheses");
|
|
else
|
|
yyerror("incomplete datatype declaration");
|
|
}
|
|
/* Possible followers for datatype in a declaration */
|
|
if (tok == K_NOT || tok == K_ASSIGN || tok == K_DEFAULT)
|
|
break;
|
|
/* Possible followers for datatype in a cursor_arg list */
|
|
if ((tok == ',' || tok == ')') && parenlevel == 0)
|
|
break;
|
|
if (tok == '(')
|
|
parenlevel++;
|
|
else if (tok == ')')
|
|
parenlevel--;
|
|
if (needspace)
|
|
plpgsql_dstring_append(&ds, " ");
|
|
needspace = true;
|
|
plpgsql_dstring_append(&ds, yytext);
|
|
|
|
tok = yylex();
|
|
}
|
|
|
|
plpgsql_push_back_token(tok);
|
|
|
|
type_name = plpgsql_dstring_get(&ds);
|
|
|
|
if (type_name[0] == '\0')
|
|
yyerror("missing datatype declaration");
|
|
|
|
plpgsql_error_lineno = lno; /* in case of error in parse_datatype */
|
|
|
|
result = plpgsql_parse_datatype(type_name);
|
|
|
|
plpgsql_dstring_free(&ds);
|
|
|
|
return result;
|
|
}
|
|
|
|
static PLpgSQL_stmt *
|
|
make_execsql_stmt(const char *sqlstart, int lineno)
|
|
{
|
|
PLpgSQL_dstring ds;
|
|
int nparams = 0;
|
|
int params[MAX_EXPR_PARAMS];
|
|
char buf[32];
|
|
PLpgSQL_stmt_execsql *execsql;
|
|
PLpgSQL_expr *expr;
|
|
PLpgSQL_row *row = NULL;
|
|
PLpgSQL_rec *rec = NULL;
|
|
int tok;
|
|
bool have_into = false;
|
|
bool have_strict = false;
|
|
|
|
plpgsql_dstring_init(&ds);
|
|
plpgsql_dstring_append(&ds, sqlstart);
|
|
|
|
for (;;)
|
|
{
|
|
tok = yylex();
|
|
if (tok == ';')
|
|
break;
|
|
if (tok == 0)
|
|
yyerror("unexpected end of function definition");
|
|
if (tok == K_INTO)
|
|
{
|
|
if (have_into)
|
|
yyerror("INTO specified more than once");
|
|
have_into = true;
|
|
read_into_target(&rec, &row, &have_strict);
|
|
continue;
|
|
}
|
|
|
|
if (plpgsql_SpaceScanned)
|
|
plpgsql_dstring_append(&ds, " ");
|
|
|
|
switch (tok)
|
|
{
|
|
case T_SCALAR:
|
|
snprintf(buf, sizeof(buf), " $%d ",
|
|
assign_expr_param(yylval.scalar->dno,
|
|
params, &nparams));
|
|
plpgsql_dstring_append(&ds, buf);
|
|
break;
|
|
|
|
case T_ROW:
|
|
snprintf(buf, sizeof(buf), " $%d ",
|
|
assign_expr_param(yylval.row->rowno,
|
|
params, &nparams));
|
|
plpgsql_dstring_append(&ds, buf);
|
|
break;
|
|
|
|
case T_RECORD:
|
|
snprintf(buf, sizeof(buf), " $%d ",
|
|
assign_expr_param(yylval.rec->recno,
|
|
params, &nparams));
|
|
plpgsql_dstring_append(&ds, buf);
|
|
break;
|
|
|
|
default:
|
|
plpgsql_dstring_append(&ds, yytext);
|
|
break;
|
|
}
|
|
}
|
|
|
|
expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
|
|
expr->dtype = PLPGSQL_DTYPE_EXPR;
|
|
expr->query = pstrdup(plpgsql_dstring_get(&ds));
|
|
expr->plan = NULL;
|
|
expr->nparams = nparams;
|
|
while(nparams-- > 0)
|
|
expr->params[nparams] = params[nparams];
|
|
plpgsql_dstring_free(&ds);
|
|
|
|
check_sql_expr(expr->query);
|
|
|
|
execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
|
|
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
|
|
execsql->lineno = lineno;
|
|
execsql->sqlstmt = expr;
|
|
execsql->into = have_into;
|
|
execsql->strict = have_strict;
|
|
execsql->rec = rec;
|
|
execsql->row = row;
|
|
|
|
return (PLpgSQL_stmt *) execsql;
|
|
}
|
|
|
|
|
|
static PLpgSQL_stmt_fetch *
|
|
read_fetch_direction(void)
|
|
{
|
|
PLpgSQL_stmt_fetch *fetch;
|
|
int tok;
|
|
bool check_FROM = true;
|
|
|
|
/*
|
|
* We create the PLpgSQL_stmt_fetch struct here, but only fill in
|
|
* the fields arising from the optional direction clause
|
|
*/
|
|
fetch = (PLpgSQL_stmt_fetch *) palloc0(sizeof(PLpgSQL_stmt_fetch));
|
|
fetch->cmd_type = PLPGSQL_STMT_FETCH;
|
|
/* set direction defaults: */
|
|
fetch->direction = FETCH_FORWARD;
|
|
fetch->how_many = 1;
|
|
fetch->expr = NULL;
|
|
|
|
/*
|
|
* Most of the direction keywords are not plpgsql keywords, so we
|
|
* rely on examining yytext ...
|
|
*/
|
|
tok = yylex();
|
|
if (tok == 0)
|
|
yyerror("unexpected end of function definition");
|
|
|
|
if (pg_strcasecmp(yytext, "next") == 0)
|
|
{
|
|
/* use defaults */
|
|
}
|
|
else if (pg_strcasecmp(yytext, "prior") == 0)
|
|
{
|
|
fetch->direction = FETCH_BACKWARD;
|
|
}
|
|
else if (pg_strcasecmp(yytext, "first") == 0)
|
|
{
|
|
fetch->direction = FETCH_ABSOLUTE;
|
|
}
|
|
else if (pg_strcasecmp(yytext, "last") == 0)
|
|
{
|
|
fetch->direction = FETCH_ABSOLUTE;
|
|
fetch->how_many = -1;
|
|
}
|
|
else if (pg_strcasecmp(yytext, "absolute") == 0)
|
|
{
|
|
fetch->direction = FETCH_ABSOLUTE;
|
|
fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN",
|
|
"SELECT ", true, true, NULL);
|
|
check_FROM = false;
|
|
}
|
|
else if (pg_strcasecmp(yytext, "relative") == 0)
|
|
{
|
|
fetch->direction = FETCH_RELATIVE;
|
|
fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN",
|
|
"SELECT ", true, true, NULL);
|
|
check_FROM = false;
|
|
}
|
|
else if (pg_strcasecmp(yytext, "forward") == 0)
|
|
{
|
|
/* use defaults */
|
|
}
|
|
else if (pg_strcasecmp(yytext, "backward") == 0)
|
|
{
|
|
fetch->direction = FETCH_BACKWARD;
|
|
}
|
|
else if (tok != T_SCALAR)
|
|
{
|
|
plpgsql_push_back_token(tok);
|
|
fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN",
|
|
"SELECT ", true, true, NULL);
|
|
check_FROM = false;
|
|
}
|
|
else
|
|
{
|
|
/* Assume there's no direction clause */
|
|
plpgsql_push_back_token(tok);
|
|
check_FROM = false;
|
|
}
|
|
|
|
/* check FROM or IN keyword after direction's specification */
|
|
if (check_FROM)
|
|
{
|
|
tok = yylex();
|
|
if (tok != K_FROM && tok != K_IN)
|
|
yyerror("expected FROM or IN");
|
|
}
|
|
|
|
return fetch;
|
|
}
|
|
|
|
|
|
static PLpgSQL_stmt *
|
|
make_return_stmt(int lineno)
|
|
{
|
|
PLpgSQL_stmt_return *new;
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_return));
|
|
new->cmd_type = PLPGSQL_STMT_RETURN;
|
|
new->lineno = lineno;
|
|
new->expr = NULL;
|
|
new->retvarno = -1;
|
|
|
|
if (plpgsql_curr_compile->fn_retset)
|
|
{
|
|
if (yylex() != ';')
|
|
yyerror("RETURN cannot have a parameter in function "
|
|
"returning set; use RETURN NEXT or RETURN QUERY");
|
|
}
|
|
else if (plpgsql_curr_compile->out_param_varno >= 0)
|
|
{
|
|
if (yylex() != ';')
|
|
yyerror("RETURN cannot have a parameter in function with OUT parameters");
|
|
new->retvarno = plpgsql_curr_compile->out_param_varno;
|
|
}
|
|
else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
|
|
{
|
|
if (yylex() != ';')
|
|
yyerror("RETURN cannot have a parameter in function returning void");
|
|
}
|
|
else if (plpgsql_curr_compile->fn_retistuple)
|
|
{
|
|
switch (yylex())
|
|
{
|
|
case K_NULL:
|
|
/* we allow this to support RETURN NULL in triggers */
|
|
break;
|
|
|
|
case T_ROW:
|
|
new->retvarno = yylval.row->rowno;
|
|
break;
|
|
|
|
case T_RECORD:
|
|
new->retvarno = yylval.rec->recno;
|
|
break;
|
|
|
|
default:
|
|
yyerror("RETURN must specify a record or row variable in function returning tuple");
|
|
break;
|
|
}
|
|
if (yylex() != ';')
|
|
yyerror("RETURN must specify a record or row variable in function returning tuple");
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Note that a well-formed expression is
|
|
* _required_ here; anything else is a
|
|
* compile-time error.
|
|
*/
|
|
new->expr = plpgsql_read_expression(';', ";");
|
|
}
|
|
|
|
return (PLpgSQL_stmt *) new;
|
|
}
|
|
|
|
|
|
static PLpgSQL_stmt *
|
|
make_return_next_stmt(int lineno)
|
|
{
|
|
PLpgSQL_stmt_return_next *new;
|
|
|
|
if (!plpgsql_curr_compile->fn_retset)
|
|
yyerror("cannot use RETURN NEXT in a non-SETOF function");
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_return_next));
|
|
new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
|
|
new->lineno = lineno;
|
|
new->expr = NULL;
|
|
new->retvarno = -1;
|
|
|
|
if (plpgsql_curr_compile->out_param_varno >= 0)
|
|
{
|
|
if (yylex() != ';')
|
|
yyerror("RETURN NEXT cannot have a parameter in function with OUT parameters");
|
|
new->retvarno = plpgsql_curr_compile->out_param_varno;
|
|
}
|
|
else if (plpgsql_curr_compile->fn_retistuple)
|
|
{
|
|
switch (yylex())
|
|
{
|
|
case T_ROW:
|
|
new->retvarno = yylval.row->rowno;
|
|
break;
|
|
|
|
case T_RECORD:
|
|
new->retvarno = yylval.rec->recno;
|
|
break;
|
|
|
|
default:
|
|
yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
|
|
break;
|
|
}
|
|
if (yylex() != ';')
|
|
yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
|
|
}
|
|
else
|
|
new->expr = plpgsql_read_expression(';', ";");
|
|
|
|
return (PLpgSQL_stmt *) new;
|
|
}
|
|
|
|
|
|
static PLpgSQL_stmt *
|
|
make_return_query_stmt(int lineno)
|
|
{
|
|
PLpgSQL_stmt_return_query *new;
|
|
|
|
if (!plpgsql_curr_compile->fn_retset)
|
|
yyerror("cannot use RETURN QUERY in a non-SETOF function");
|
|
|
|
new = palloc0(sizeof(PLpgSQL_stmt_return_query));
|
|
new->cmd_type = PLPGSQL_STMT_RETURN_QUERY;
|
|
new->lineno = lineno;
|
|
new->query = read_sql_construct(';', 0, ")", "", false, true, NULL);
|
|
|
|
return (PLpgSQL_stmt *) new;
|
|
}
|
|
|
|
|
|
static void
|
|
check_assignable(PLpgSQL_datum *datum)
|
|
{
|
|
switch (datum->dtype)
|
|
{
|
|
case PLPGSQL_DTYPE_VAR:
|
|
if (((PLpgSQL_var *) datum)->isconst)
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
|
|
errmsg("\"%s\" is declared CONSTANT",
|
|
((PLpgSQL_var *) datum)->refname)));
|
|
}
|
|
break;
|
|
case PLPGSQL_DTYPE_ROW:
|
|
/* always assignable? */
|
|
break;
|
|
case PLPGSQL_DTYPE_REC:
|
|
/* always assignable? What about NEW/OLD? */
|
|
break;
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
|
/* always assignable? */
|
|
break;
|
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
|
/* always assignable? */
|
|
break;
|
|
case PLPGSQL_DTYPE_TRIGARG:
|
|
yyerror("cannot assign to tg_argv");
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read the argument of an INTO clause. On entry, we have just read the
|
|
* INTO keyword.
|
|
*/
|
|
static void
|
|
read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict)
|
|
{
|
|
int tok;
|
|
|
|
/* Set default results */
|
|
*rec = NULL;
|
|
*row = NULL;
|
|
if (strict)
|
|
*strict = false;
|
|
|
|
tok = yylex();
|
|
if (strict && tok == K_STRICT)
|
|
{
|
|
*strict = true;
|
|
tok = yylex();
|
|
}
|
|
|
|
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);
|
|
break;
|
|
|
|
default:
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("syntax error at \"%s\"", yytext),
|
|
errdetail("Expected record variable, row variable, "
|
|
"or list of scalar variables following INTO.")));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Given the first datum and name in the INTO list, continue to read
|
|
* comma-separated scalar variables until we run out. Then construct
|
|
* and return a fake "row" variable that represents the list of
|
|
* scalars.
|
|
*/
|
|
static PLpgSQL_row *
|
|
read_into_scalar_list(const char *initial_name,
|
|
PLpgSQL_datum *initial_datum)
|
|
{
|
|
int nfields;
|
|
char *fieldnames[1024];
|
|
int varnos[1024];
|
|
PLpgSQL_row *row;
|
|
int tok;
|
|
|
|
check_assignable(initial_datum);
|
|
fieldnames[0] = pstrdup(initial_name);
|
|
varnos[0] = initial_datum->dno;
|
|
nfields = 1;
|
|
|
|
while ((tok = yylex()) == ',')
|
|
{
|
|
/* Check for array overflow */
|
|
if (nfields >= 1024)
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
errmsg("too many INTO variables specified")));
|
|
}
|
|
|
|
tok = yylex();
|
|
switch(tok)
|
|
{
|
|
case T_SCALAR:
|
|
check_assignable(yylval.scalar);
|
|
fieldnames[nfields] = pstrdup(yytext);
|
|
varnos[nfields++] = yylval.scalar->dno;
|
|
break;
|
|
|
|
default:
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("\"%s\" is not a scalar variable",
|
|
yytext)));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We read an extra, non-comma token from yylex(), so push it
|
|
* back onto the input stream
|
|
*/
|
|
plpgsql_push_back_token(tok);
|
|
|
|
row = palloc(sizeof(PLpgSQL_row));
|
|
row->dtype = PLPGSQL_DTYPE_ROW;
|
|
row->refname = pstrdup("*internal*");
|
|
row->lineno = plpgsql_scanner_lineno();
|
|
row->rowtupdesc = NULL;
|
|
row->nfields = nfields;
|
|
row->fieldnames = palloc(sizeof(char *) * nfields);
|
|
row->varnos = palloc(sizeof(int) * nfields);
|
|
while (--nfields >= 0)
|
|
{
|
|
row->fieldnames[nfields] = fieldnames[nfields];
|
|
row->varnos[nfields] = varnos[nfields];
|
|
}
|
|
|
|
plpgsql_adddatum((PLpgSQL_datum *)row);
|
|
|
|
return row;
|
|
}
|
|
|
|
/*
|
|
* Convert a single scalar into a "row" list. This is exactly
|
|
* like read_into_scalar_list except we never consume any input.
|
|
* In fact, since this can be invoked long after the source
|
|
* input was actually read, the lineno has to be passed in.
|
|
*/
|
|
static PLpgSQL_row *
|
|
make_scalar_list1(const char *initial_name,
|
|
PLpgSQL_datum *initial_datum,
|
|
int lineno)
|
|
{
|
|
PLpgSQL_row *row;
|
|
|
|
check_assignable(initial_datum);
|
|
|
|
row = palloc(sizeof(PLpgSQL_row));
|
|
row->dtype = PLPGSQL_DTYPE_ROW;
|
|
row->refname = pstrdup("*internal*");
|
|
row->lineno = lineno;
|
|
row->rowtupdesc = NULL;
|
|
row->nfields = 1;
|
|
row->fieldnames = palloc(sizeof(char *));
|
|
row->varnos = palloc(sizeof(int));
|
|
row->fieldnames[0] = pstrdup(initial_name);
|
|
row->varnos[0] = initial_datum->dno;
|
|
|
|
plpgsql_adddatum((PLpgSQL_datum *)row);
|
|
|
|
return row;
|
|
}
|
|
|
|
/*
|
|
* When the PL/PgSQL parser expects to see a SQL statement, it is very
|
|
* liberal in what it accepts; for example, we often assume an
|
|
* unrecognized keyword is the beginning of a SQL statement. This
|
|
* avoids the need to duplicate parts of the SQL grammar in the
|
|
* PL/PgSQL grammar, but it means we can accept wildly malformed
|
|
* input. To try and catch some of the more obviously invalid input,
|
|
* we run the strings we expect to be SQL statements through the main
|
|
* SQL parser.
|
|
*
|
|
* We only invoke the raw parser (not the analyzer); this doesn't do
|
|
* any database access and does not check any semantic rules, it just
|
|
* checks for basic syntactic correctness. We do this here, rather
|
|
* than after parsing has finished, because a malformed SQL statement
|
|
* may cause the PL/PgSQL parser to become confused about statement
|
|
* borders. So it is best to bail out as early as we can.
|
|
*/
|
|
static void
|
|
check_sql_expr(const char *stmt)
|
|
{
|
|
ErrorContextCallback syntax_errcontext;
|
|
ErrorContextCallback *previous_errcontext;
|
|
MemoryContext oldCxt;
|
|
|
|
if (!plpgsql_check_syntax)
|
|
return;
|
|
|
|
/*
|
|
* Setup error traceback support for ereport(). The previous
|
|
* ereport callback is installed by pl_comp.c, but we don't want
|
|
* that to be invoked (since it will try to transpose the syntax
|
|
* error to be relative to the CREATE FUNCTION), so temporarily
|
|
* remove it from the list of callbacks.
|
|
*/
|
|
Assert(error_context_stack->callback == plpgsql_compile_error_callback);
|
|
|
|
previous_errcontext = error_context_stack;
|
|
syntax_errcontext.callback = plpgsql_sql_error_callback;
|
|
syntax_errcontext.arg = (char *) stmt;
|
|
syntax_errcontext.previous = error_context_stack->previous;
|
|
error_context_stack = &syntax_errcontext;
|
|
|
|
oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
|
|
(void) raw_parser(stmt);
|
|
MemoryContextSwitchTo(oldCxt);
|
|
|
|
/* Restore former ereport callback */
|
|
error_context_stack = previous_errcontext;
|
|
}
|
|
|
|
static void
|
|
plpgsql_sql_error_callback(void *arg)
|
|
{
|
|
char *sql_stmt = (char *) arg;
|
|
|
|
Assert(plpgsql_error_funcname);
|
|
|
|
errcontext("SQL statement in PL/PgSQL function \"%s\" near line %d",
|
|
plpgsql_error_funcname, plpgsql_error_lineno);
|
|
internalerrquery(sql_stmt);
|
|
internalerrposition(geterrposition());
|
|
errposition(0);
|
|
}
|
|
|
|
static void
|
|
check_labels(const char *start_label, const char *end_label)
|
|
{
|
|
if (end_label)
|
|
{
|
|
if (!start_label)
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("end label \"%s\" specified for unlabelled block",
|
|
end_label)));
|
|
}
|
|
|
|
if (strcmp(start_label, end_label) != 0)
|
|
{
|
|
plpgsql_error_lineno = plpgsql_scanner_lineno();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("end label \"%s\" differs from block's label \"%s\"",
|
|
end_label, start_label)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Needed to avoid conflict between different prefix settings: */
|
|
#undef yylex
|
|
|
|
#include "pl_scan.c"
|
|
|