postgresql/src/pl/plpgsql/src/pl_gram.y

4121 lines
107 KiB
Plaintext

%{
/*-------------------------------------------------------------------------
*
* pl_gram.y - Parser for the PL/pgSQL procedural language
*
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/pl/plpgsql/src/pl_gram.y
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "parser/parser.h"
#include "parser/parse_type.h"
#include "parser/scanner.h"
#include "parser/scansup.h"
#include "utils/builtins.h"
#include "plpgsql.h"
/* Location tracking support --- simpler than bison's default */
#define YYLLOC_DEFAULT(Current, Rhs, N) \
do { \
if (N) \
(Current) = (Rhs)[1]; \
else \
(Current) = (Rhs)[0]; \
} while (0)
/*
* Bison doesn't allocate anything that needs to live across parser calls,
* so we can easily have it use palloc instead of malloc. This prevents
* memory leaks if we error out during parsing. Note this only works with
* bison >= 2.0. However, in bison 1.875 the default is to use alloca()
* if possible, so there's not really much problem anyhow, at least if
* you're building with gcc.
*/
#define YYMALLOC palloc
#define YYFREE pfree
typedef struct
{
int location;
int leaderlen;
} sql_error_callback_arg;
#define parser_errposition(pos) plpgsql_scanner_errposition(pos)
union YYSTYPE; /* need forward reference for tok_is_keyword */
static bool tok_is_keyword(int token, union YYSTYPE *lval,
int kw_token, const char *kw_str);
static void word_is_not_variable(PLword *word, int location);
static void cword_is_not_variable(PLcword *cword, int location);
static void current_token_is_not_variable(int tok);
static PLpgSQL_expr *read_sql_construct(int until,
int until2,
int until3,
const char *expected,
const char *sqlstart,
bool isexpression,
bool valid_sql,
bool trim,
int *startloc,
int *endtoken);
static PLpgSQL_expr *read_sql_expression(int until,
const char *expected);
static PLpgSQL_expr *read_sql_expression2(int until, int until2,
const char *expected,
int *endtoken);
static PLpgSQL_expr *read_sql_stmt(const char *sqlstart);
static PLpgSQL_type *read_datatype(int tok);
static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location);
static PLpgSQL_stmt_fetch *read_fetch_direction(void);
static void complete_direction(PLpgSQL_stmt_fetch *fetch,
bool *check_FROM);
static PLpgSQL_stmt *make_return_stmt(int location);
static PLpgSQL_stmt *make_return_next_stmt(int location);
static PLpgSQL_stmt *make_return_query_stmt(int location);
static PLpgSQL_stmt *make_case(int location, PLpgSQL_expr *t_expr,
List *case_when_list, List *else_stmts);
static char *NameOfDatum(PLwdatum *wdatum);
static void check_assignable(PLpgSQL_datum *datum, int location);
static void read_into_target(PLpgSQL_variable **target,
bool *strict);
static PLpgSQL_row *read_into_scalar_list(char *initial_name,
PLpgSQL_datum *initial_datum,
int initial_location);
static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location);
static void check_sql_expr(const char *stmt, int location,
int leaderlen);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location);
static void check_labels(const char *start_label,
const char *end_label,
int end_location);
static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
int until, const char *expected);
static List *read_raise_options(void);
static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%}
%expect 0
%name-prefix="plpgsql_yy"
%locations
%union {
core_YYSTYPE core_yystype;
/* these fields must match core_YYSTYPE: */
int ival;
char *str;
const char *keyword;
PLword word;
PLcword cword;
PLwdatum wdatum;
bool boolean;
Oid oid;
struct
{
char *name;
int lineno;
} varname;
struct
{
char *name;
int lineno;
PLpgSQL_datum *scalar;
PLpgSQL_datum *row;
} forvariable;
struct
{
char *label;
int n_initvars;
int *initvarnos;
} declhdr;
struct
{
List *stmts;
char *end_label;
int end_label_location;
} loop_body;
List *list;
PLpgSQL_type *dtype;
PLpgSQL_datum *datum;
PLpgSQL_var *var;
PLpgSQL_expr *expr;
PLpgSQL_stmt *stmt;
PLpgSQL_condition *condition;
PLpgSQL_exception *exception;
PLpgSQL_exception_block *exception_block;
PLpgSQL_nsitem *nsitem;
PLpgSQL_diag_item *diagitem;
PLpgSQL_stmt_fetch *fetch;
PLpgSQL_case_when *casewhen;
}
%type <declhdr> decl_sect
%type <varname> decl_varname
%type <boolean> decl_const decl_notnull exit_type
%type <expr> decl_defval decl_cursor_query
%type <dtype> decl_datatype
%type <oid> decl_collate
%type <datum> decl_cursor_args
%type <list> decl_cursor_arglist
%type <nsitem> decl_aliasitem
%type <expr> expr_until_semi expr_until_rightbracket
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
%type <datum> assign_var
%type <var> cursor_variable
%type <datum> decl_cursor_arg
%type <forvariable> for_variable
%type <ival> foreach_slice
%type <stmt> for_control
%type <str> any_identifier opt_block_label opt_loop_label opt_label
%type <str> option_value
%type <list> proc_sect stmt_elsifs 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_assert stmt_execsql
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
%type <stmt> stmt_commit stmt_rollback stmt_set
%type <stmt> stmt_case stmt_foreach_a
%type <list> proc_exceptions
%type <exception_block> exception_sect
%type <exception> proc_exception
%type <condition> proc_conditions proc_condition
%type <casewhen> case_when
%type <list> case_when_list opt_case_else
%type <boolean> getdiag_area_opt
%type <list> getdiag_list
%type <diagitem> getdiag_list_item
%type <datum> getdiag_target
%type <ival> getdiag_item
%type <ival> opt_scrollable
%type <fetch> opt_fetch_direction
%type <ival> opt_transaction_chain
%type <keyword> unreserved_keyword
/*
* Basic non-keyword token types. These are hard-wired into the core lexer.
* They must be listed first so that their numeric codes do not depend on
* the set of keywords. Keep this list in sync with backend/parser/gram.y!
*
* Some of these are not directly referenced in this file, but they must be
* here anyway.
*/
%token <str> IDENT UIDENT FCONST SCONST USCONST BCONST XCONST Op
%token <ival> ICONST PARAM
%token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
%token LESS_EQUALS GREATER_EQUALS NOT_EQUALS
/*
* Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c).
*/
%token <word> T_WORD /* unrecognized simple identifier */
%token <cword> T_CWORD /* unrecognized composite identifier */
%token <wdatum> T_DATUM /* a VAR, ROW, REC, or RECFIELD variable */
%token LESS_LESS
%token GREATER_GREATER
/*
* Keyword tokens. Some of these are reserved and some are not;
* see pl_scanner.c for info. Be sure unreserved keywords are listed
* in the "unreserved_keyword" production below.
*/
%token <keyword> K_ABSOLUTE
%token <keyword> K_ALIAS
%token <keyword> K_ALL
%token <keyword> K_AND
%token <keyword> K_ARRAY
%token <keyword> K_ASSERT
%token <keyword> K_BACKWARD
%token <keyword> K_BEGIN
%token <keyword> K_BY
%token <keyword> K_CALL
%token <keyword> K_CASE
%token <keyword> K_CHAIN
%token <keyword> K_CLOSE
%token <keyword> K_COLLATE
%token <keyword> K_COLUMN
%token <keyword> K_COLUMN_NAME
%token <keyword> K_COMMIT
%token <keyword> K_CONSTANT
%token <keyword> K_CONSTRAINT
%token <keyword> K_CONSTRAINT_NAME
%token <keyword> K_CONTINUE
%token <keyword> K_CURRENT
%token <keyword> K_CURSOR
%token <keyword> K_DATATYPE
%token <keyword> K_DEBUG
%token <keyword> K_DECLARE
%token <keyword> K_DEFAULT
%token <keyword> K_DETAIL
%token <keyword> K_DIAGNOSTICS
%token <keyword> K_DO
%token <keyword> K_DUMP
%token <keyword> K_ELSE
%token <keyword> K_ELSIF
%token <keyword> K_END
%token <keyword> K_ERRCODE
%token <keyword> K_ERROR
%token <keyword> K_EXCEPTION
%token <keyword> K_EXECUTE
%token <keyword> K_EXIT
%token <keyword> K_FETCH
%token <keyword> K_FIRST
%token <keyword> K_FOR
%token <keyword> K_FOREACH
%token <keyword> K_FORWARD
%token <keyword> K_FROM
%token <keyword> K_GET
%token <keyword> K_HINT
%token <keyword> K_IF
%token <keyword> K_IMPORT
%token <keyword> K_IN
%token <keyword> K_INFO
%token <keyword> K_INSERT
%token <keyword> K_INTO
%token <keyword> K_IS
%token <keyword> K_LAST
%token <keyword> K_LOG
%token <keyword> K_LOOP
%token <keyword> K_MESSAGE
%token <keyword> K_MESSAGE_TEXT
%token <keyword> K_MOVE
%token <keyword> K_NEXT
%token <keyword> K_NO
%token <keyword> K_NOT
%token <keyword> K_NOTICE
%token <keyword> K_NULL
%token <keyword> K_OPEN
%token <keyword> K_OPTION
%token <keyword> K_OR
%token <keyword> K_PERFORM
%token <keyword> K_PG_CONTEXT
%token <keyword> K_PG_DATATYPE_NAME
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
%token <keyword> K_PRINT_STRICT_PARAMS
%token <keyword> K_PRIOR
%token <keyword> K_QUERY
%token <keyword> K_RAISE
%token <keyword> K_RELATIVE
%token <keyword> K_RESET
%token <keyword> K_RETURN
%token <keyword> K_RETURNED_SQLSTATE
%token <keyword> K_REVERSE
%token <keyword> K_ROLLBACK
%token <keyword> K_ROW_COUNT
%token <keyword> K_ROWTYPE
%token <keyword> K_SCHEMA
%token <keyword> K_SCHEMA_NAME
%token <keyword> K_SCROLL
%token <keyword> K_SET
%token <keyword> K_SLICE
%token <keyword> K_SQLSTATE
%token <keyword> K_STACKED
%token <keyword> K_STRICT
%token <keyword> K_TABLE
%token <keyword> K_TABLE_NAME
%token <keyword> K_THEN
%token <keyword> K_TO
%token <keyword> K_TYPE
%token <keyword> K_USE_COLUMN
%token <keyword> K_USE_VARIABLE
%token <keyword> K_USING
%token <keyword> K_VARIABLE_CONFLICT
%token <keyword> K_WARNING
%token <keyword> K_WHEN
%token <keyword> K_WHILE
%%
pl_function : comp_options pl_block opt_semi
{
plpgsql_parse_result = (PLpgSQL_stmt_block *) $2;
}
;
comp_options :
| comp_options comp_option
;
comp_option : '#' K_OPTION K_DUMP
{
plpgsql_DumpExecTree = true;
}
| '#' K_PRINT_STRICT_PARAMS option_value
{
if (strcmp($3, "on") == 0)
plpgsql_curr_compile->print_strict_params = true;
else if (strcmp($3, "off") == 0)
plpgsql_curr_compile->print_strict_params = false;
else
elog(ERROR, "unrecognized print_strict_params option %s", $3);
}
| '#' K_VARIABLE_CONFLICT K_ERROR
{
plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_ERROR;
}
| '#' K_VARIABLE_CONFLICT K_USE_VARIABLE
{
plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_VARIABLE;
}
| '#' K_VARIABLE_CONFLICT K_USE_COLUMN
{
plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_COLUMN;
}
;
option_value : T_WORD
{
$$ = $1.ident;
}
| unreserved_keyword
{
$$ = pstrdup($1);
}
opt_semi :
| ';'
;
pl_block : decl_sect K_BEGIN 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 = plpgsql_location_to_lineno(@2);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->label = $1.label;
new->n_initvars = $1.n_initvars;
new->initvarnos = $1.initvarnos;
new->body = $3;
new->exceptions = $4;
check_labels($1.label, $6, @6);
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)new;
}
;
decl_sect : opt_block_label
{
/* done with decls, so resume identifier lookup */
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
$$.label = $1;
$$.n_initvars = 0;
$$.initvarnos = NULL;
}
| opt_block_label decl_start
{
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
$$.label = $1;
$$.n_initvars = 0;
$$.initvarnos = NULL;
}
| opt_block_label decl_start decl_stmts
{
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
$$.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);
/*
* Disable scanner lookup of identifiers while
* we process the decl_stmts
*/
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
}
;
decl_stmts : decl_stmts decl_stmt
| decl_stmt
;
decl_stmt : decl_statement
| K_DECLARE
{
/* We allow useless extra DECLAREs */
}
| LESS_LESS any_identifier GREATER_GREATER
{
/*
* Throw a helpful error if user tries to put block
* label just before BEGIN, instead of before DECLARE.
*/
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("block label must be placed before DECLARE, not after"),
parser_errposition(@1)));
}
;
decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull decl_defval
{
PLpgSQL_variable *var;
/*
* If a collation is supplied, insert it into the
* datatype. We assume decl_datatype always returns
* a freshly built struct not shared with other
* variables.
*/
if (OidIsValid($4))
{
if (!OidIsValid($3->collation))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("collations are not supported by type %s",
format_type_be($3->typoid)),
parser_errposition(@4)));
$3->collation = $4;
}
var = plpgsql_build_variable($1.name, $1.lineno,
$3, true);
var->isconst = $2;
var->notnull = $5;
var->default_val = $6;
/*
* The combination of NOT NULL without an initializer
* can't work, so let's reject it at compile time.
*/
if (var->notnull && var->default_val == NULL)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("variable \"%s\" must have a default value, since it's declared NOT NULL",
var->refname),
parser_errposition(@5)));
}
| decl_varname K_ALIAS K_FOR decl_aliasitem ';'
{
plpgsql_ns_additem($4->itemtype,
$4->itemno, $1.name);
}
| decl_varname opt_scrollable K_CURSOR
{ plpgsql_ns_push($1.name, PLPGSQL_LABEL_OTHER); }
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,
InvalidOid,
NULL),
true);
curname_def = palloc0(sizeof(PLpgSQL_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, "'::pg_catalog.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->dno;
new->cursor_options = CURSOR_OPT_FAST_PLAN | $2;
}
;
opt_scrollable :
{
$$ = 0;
}
| K_NO K_SCROLL
{
$$ = CURSOR_OPT_NO_SCROLL;
}
| K_SCROLL
{
$$ = CURSOR_OPT_SCROLL;
}
;
decl_cursor_query :
{
$$ = read_sql_stmt("");
}
;
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->refname = "(unnamed row)";
new->lineno = plpgsql_location_to_lineno(@1);
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);
Assert(!arg->isconst);
new->fieldnames[i] = arg->refname;
new->varnos[i] = arg->dno;
i++;
}
list_free($2);
plpgsql_adddatum((PLpgSQL_datum *) new);
$$ = (PLpgSQL_datum *) 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_datum *)
plpgsql_build_variable($1.name, $1.lineno,
$2, true);
}
;
decl_is_for : K_IS | /* Oracle */
K_FOR; /* SQL standard */
decl_aliasitem : T_WORD
{
PLpgSQL_nsitem *nsi;
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
$1.ident, NULL, NULL,
NULL);
if (nsi == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("variable \"%s\" does not exist",
$1.ident),
parser_errposition(@1)));
$$ = nsi;
}
| unreserved_keyword
{
PLpgSQL_nsitem *nsi;
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
$1, NULL, NULL,
NULL);
if (nsi == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("variable \"%s\" does not exist",
$1),
parser_errposition(@1)));
$$ = nsi;
}
| T_CWORD
{
PLpgSQL_nsitem *nsi;
if (list_length($1.idents) == 2)
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
strVal(linitial($1.idents)),
strVal(lsecond($1.idents)),
NULL,
NULL);
else if (list_length($1.idents) == 3)
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
strVal(linitial($1.idents)),
strVal(lsecond($1.idents)),
strVal(lthird($1.idents)),
NULL);
else
nsi = NULL;
if (nsi == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("variable \"%s\" does not exist",
NameListToString($1.idents)),
parser_errposition(@1)));
$$ = nsi;
}
;
decl_varname : T_WORD
{
$$.name = $1.ident;
$$.lineno = plpgsql_location_to_lineno(@1);
/*
* Check to make sure name isn't already declared
* in the current block.
*/
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
$1.ident, NULL, NULL,
NULL) != NULL)
yyerror("duplicate declaration");
if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR ||
plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR)
{
PLpgSQL_nsitem *nsi;
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
$1.ident, NULL, NULL, NULL);
if (nsi != NULL)
ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING,
(errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("variable \"%s\" shadows a previously defined variable",
$1.ident),
parser_errposition(@1)));
}
}
| unreserved_keyword
{
$$.name = pstrdup($1);
$$.lineno = plpgsql_location_to_lineno(@1);
/*
* Check to make sure name isn't already declared
* in the current block.
*/
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
$1, NULL, NULL,
NULL) != NULL)
yyerror("duplicate declaration");
if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR ||
plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR)
{
PLpgSQL_nsitem *nsi;
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
$1, NULL, NULL, NULL);
if (nsi != NULL)
ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING,
(errcode(ERRCODE_DUPLICATE_ALIAS),
errmsg("variable \"%s\" shadows a previously defined variable",
$1),
parser_errposition(@1)));
}
}
;
decl_const :
{ $$ = false; }
| K_CONSTANT
{ $$ = true; }
;
decl_datatype :
{
/*
* If there's a lookahead token, read_datatype
* should consume it.
*/
$$ = read_datatype(yychar);
yyclearin;
}
;
decl_collate :
{ $$ = InvalidOid; }
| K_COLLATE T_WORD
{
$$ = get_collation_oid(list_make1(makeString($2.ident)),
false);
}
| K_COLLATE unreserved_keyword
{
$$ = get_collation_oid(list_make1(makeString(pstrdup($2))),
false);
}
| K_COLLATE T_CWORD
{
$$ = get_collation_oid($2.idents, false);
}
;
decl_notnull :
{ $$ = false; }
| K_NOT K_NULL
{ $$ = true; }
;
decl_defval : ';'
{ $$ = NULL; }
| decl_defkey
{
$$ = read_sql_expression(';', ";");
}
;
decl_defkey : assign_operator
| K_DEFAULT
;
/*
* Ada-based PL/SQL uses := for assignment and variable defaults, while
* the SQL standard uses equals for these cases and for GET
* DIAGNOSTICS, so we support both. FOR and OPEN only support :=.
*/
assign_operator : '='
| COLON_EQUALS
;
proc_sect :
{ $$ = NIL; }
| proc_sect proc_stmt
{
/* don't bother linking null statements into list */
if ($2 == NULL)
$$ = $1;
else
$$ = lappend($1, $2);
}
;
proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_assign
{ $$ = $1; }
| stmt_if
{ $$ = $1; }
| stmt_case
{ $$ = $1; }
| stmt_loop
{ $$ = $1; }
| stmt_while
{ $$ = $1; }
| stmt_for
{ $$ = $1; }
| stmt_foreach_a
{ $$ = $1; }
| stmt_exit
{ $$ = $1; }
| stmt_return
{ $$ = $1; }
| stmt_raise
{ $$ = $1; }
| stmt_assert
{ $$ = $1; }
| stmt_execsql
{ $$ = $1; }
| stmt_dynexecute
{ $$ = $1; }
| stmt_perform
{ $$ = $1; }
| stmt_call
{ $$ = $1; }
| stmt_getdiag
{ $$ = $1; }
| stmt_open
{ $$ = $1; }
| stmt_fetch
{ $$ = $1; }
| stmt_move
{ $$ = $1; }
| stmt_close
{ $$ = $1; }
| stmt_null
{ $$ = $1; }
| stmt_commit
{ $$ = $1; }
| stmt_rollback
{ $$ = $1; }
| stmt_set
{ $$ = $1; }
;
stmt_perform : K_PERFORM expr_until_semi
{
PLpgSQL_stmt_perform *new;
new = palloc0(sizeof(PLpgSQL_stmt_perform));
new->cmd_type = PLPGSQL_STMT_PERFORM;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->expr = $2;
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_call : K_CALL
{
PLpgSQL_stmt_call *new;
new = palloc0(sizeof(PLpgSQL_stmt_call));
new->cmd_type = PLPGSQL_STMT_CALL;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->expr = read_sql_stmt("CALL ");
new->is_call = true;
$$ = (PLpgSQL_stmt *)new;
}
| K_DO
{
/* use the same structures as for CALL, for simplicity */
PLpgSQL_stmt_call *new;
new = palloc0(sizeof(PLpgSQL_stmt_call));
new->cmd_type = PLPGSQL_STMT_CALL;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->expr = read_sql_stmt("DO ");
new->is_call = false;
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_assign : assign_var assign_operator expr_until_semi
{
PLpgSQL_stmt_assign *new;
new = palloc0(sizeof(PLpgSQL_stmt_assign));
new->cmd_type = PLPGSQL_STMT_ASSIGN;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->varno = $1->dno;
new->expr = $3;
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
{
PLpgSQL_stmt_getdiag *new;
ListCell *lc;
new = palloc0(sizeof(PLpgSQL_stmt_getdiag));
new->cmd_type = PLPGSQL_STMT_GETDIAG;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->is_stacked = $2;
new->diag_items = $4;
/*
* Check information items are valid for area option.
*/
foreach(lc, new->diag_items)
{
PLpgSQL_diag_item *ditem = (PLpgSQL_diag_item *) lfirst(lc);
switch (ditem->kind)
{
/* these fields are disallowed in stacked case */
case PLPGSQL_GETDIAG_ROW_COUNT:
if (new->is_stacked)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS",
plpgsql_getdiag_kindname(ditem->kind)),
parser_errposition(@1)));
break;
/* these fields are disallowed in current case */
case PLPGSQL_GETDIAG_ERROR_CONTEXT:
case PLPGSQL_GETDIAG_ERROR_DETAIL:
case PLPGSQL_GETDIAG_ERROR_HINT:
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
case PLPGSQL_GETDIAG_COLUMN_NAME:
case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
case PLPGSQL_GETDIAG_DATATYPE_NAME:
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
case PLPGSQL_GETDIAG_TABLE_NAME:
case PLPGSQL_GETDIAG_SCHEMA_NAME:
if (!new->is_stacked)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS",
plpgsql_getdiag_kindname(ditem->kind)),
parser_errposition(@1)));
break;
/* these fields are allowed in either case */
case PLPGSQL_GETDIAG_CONTEXT:
break;
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
ditem->kind);
break;
}
}
$$ = (PLpgSQL_stmt *)new;
}
;
getdiag_area_opt :
{
$$ = false;
}
| K_CURRENT
{
$$ = false;
}
| K_STACKED
{
$$ = true;
}
;
getdiag_list : getdiag_list ',' getdiag_list_item
{
$$ = lappend($1, $3);
}
| getdiag_list_item
{
$$ = list_make1($1);
}
;
getdiag_list_item : getdiag_target assign_operator getdiag_item
{
PLpgSQL_diag_item *new;
new = palloc(sizeof(PLpgSQL_diag_item));
new->target = $1->dno;
new->kind = $3;
$$ = new;
}
;
getdiag_item :
{
int tok = yylex();
if (tok_is_keyword(tok, &yylval,
K_ROW_COUNT, "row_count"))
$$ = PLPGSQL_GETDIAG_ROW_COUNT;
else if (tok_is_keyword(tok, &yylval,
K_PG_CONTEXT, "pg_context"))
$$ = PLPGSQL_GETDIAG_CONTEXT;
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_DETAIL, "pg_exception_detail"))
$$ = PLPGSQL_GETDIAG_ERROR_DETAIL;
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_HINT, "pg_exception_hint"))
$$ = PLPGSQL_GETDIAG_ERROR_HINT;
else if (tok_is_keyword(tok, &yylval,
K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
$$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
else if (tok_is_keyword(tok, &yylval,
K_COLUMN_NAME, "column_name"))
$$ = PLPGSQL_GETDIAG_COLUMN_NAME;
else if (tok_is_keyword(tok, &yylval,
K_CONSTRAINT_NAME, "constraint_name"))
$$ = PLPGSQL_GETDIAG_CONSTRAINT_NAME;
else if (tok_is_keyword(tok, &yylval,
K_PG_DATATYPE_NAME, "pg_datatype_name"))
$$ = PLPGSQL_GETDIAG_DATATYPE_NAME;
else if (tok_is_keyword(tok, &yylval,
K_MESSAGE_TEXT, "message_text"))
$$ = PLPGSQL_GETDIAG_MESSAGE_TEXT;
else if (tok_is_keyword(tok, &yylval,
K_TABLE_NAME, "table_name"))
$$ = PLPGSQL_GETDIAG_TABLE_NAME;
else if (tok_is_keyword(tok, &yylval,
K_SCHEMA_NAME, "schema_name"))
$$ = PLPGSQL_GETDIAG_SCHEMA_NAME;
else if (tok_is_keyword(tok, &yylval,
K_RETURNED_SQLSTATE, "returned_sqlstate"))
$$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
else
yyerror("unrecognized GET DIAGNOSTICS item");
}
;
getdiag_target : assign_var
{
if ($1->dtype == PLPGSQL_DTYPE_ROW ||
$1->dtype == PLPGSQL_DTYPE_REC)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
((PLpgSQL_variable *) $1)->refname),
parser_errposition(@1)));
$$ = $1;
}
| T_WORD
{
/* just to give a better message than "syntax error" */
word_is_not_variable(&($1), @1);
}
| T_CWORD
{
/* just to give a better message than "syntax error" */
cword_is_not_variable(&($1), @1);
}
;
assign_var : T_DATUM
{
check_assignable($1.datum, @1);
$$ = $1.datum;
}
| assign_var '[' expr_until_rightbracket
{
PLpgSQL_arrayelem *new;
new = palloc0(sizeof(PLpgSQL_arrayelem));
new->dtype = PLPGSQL_DTYPE_ARRAYELEM;
new->subscript = $3;
new->arrayparentno = $1->dno;
/* initialize cached type data to "not valid" */
new->parenttypoid = InvalidOid;
plpgsql_adddatum((PLpgSQL_datum *) new);
$$ = (PLpgSQL_datum *) new;
}
;
stmt_if : K_IF expr_until_then proc_sect stmt_elsifs stmt_else K_END K_IF ';'
{
PLpgSQL_stmt_if *new;
new = palloc0(sizeof(PLpgSQL_stmt_if));
new->cmd_type = PLPGSQL_STMT_IF;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->cond = $2;
new->then_body = $3;
new->elsif_list = $4;
new->else_body = $5;
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_elsifs :
{
$$ = NIL;
}
| stmt_elsifs K_ELSIF expr_until_then proc_sect
{
PLpgSQL_if_elsif *new;
new = palloc0(sizeof(PLpgSQL_if_elsif));
new->lineno = plpgsql_location_to_lineno(@2);
new->cond = $3;
new->stmts = $4;
$$ = lappend($1, new);
}
;
stmt_else :
{
$$ = NIL;
}
| K_ELSE proc_sect
{
$$ = $2;
}
;
stmt_case : K_CASE opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';'
{
$$ = make_case(@1, $2, $3, $4);
}
;
opt_expr_until_when :
{
PLpgSQL_expr *expr = NULL;
int tok = yylex();
if (tok != K_WHEN)
{
plpgsql_push_back_token(tok);
expr = read_sql_expression(K_WHEN, "WHEN");
}
plpgsql_push_back_token(K_WHEN);
$$ = expr;
}
;
case_when_list : case_when_list case_when
{
$$ = lappend($1, $2);
}
| case_when
{
$$ = list_make1($1);
}
;
case_when : K_WHEN expr_until_then proc_sect
{
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
new->lineno = plpgsql_location_to_lineno(@1);
new->expr = $2;
new->stmts = $3;
$$ = new;
}
;
opt_case_else :
{
$$ = NIL;
}
| K_ELSE proc_sect
{
/*
* proc_sect could return an empty list, but we
* must distinguish that from not having ELSE at all.
* Simplest fix is to return a list with one NULL
* pointer, which make_case() must take care of.
*/
if ($2 != NIL)
$$ = $2;
else
$$ = list_make1(NULL);
}
;
stmt_loop : opt_loop_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *new;
new = palloc0(sizeof(PLpgSQL_stmt_loop));
new->cmd_type = PLPGSQL_STMT_LOOP;
new->lineno = plpgsql_location_to_lineno(@2);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->label = $1;
new->body = $3.stmts;
check_labels($1, $3.end_label, $3.end_label_location);
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_while : opt_loop_label K_WHILE expr_until_loop loop_body
{
PLpgSQL_stmt_while *new;
new = palloc0(sizeof(PLpgSQL_stmt_while));
new->cmd_type = PLPGSQL_STMT_WHILE;
new->lineno = plpgsql_location_to_lineno(@2);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->label = $1;
new->cond = $3;
new->body = $4.stmts;
check_labels($1, $4.end_label, $4.end_label_location);
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_for : opt_loop_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->lineno = plpgsql_location_to_lineno(@2);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->label = $1;
new->body = $4.stmts;
$$ = (PLpgSQL_stmt *) new;
}
else
{
PLpgSQL_stmt_forq *new;
Assert($3->cmd_type == PLPGSQL_STMT_FORS ||
$3->cmd_type == PLPGSQL_STMT_FORC ||
$3->cmd_type == PLPGSQL_STMT_DYNFORS);
/* forq is the common supertype of all three */
new = (PLpgSQL_stmt_forq *) $3;
new->lineno = plpgsql_location_to_lineno(@2);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->label = $1;
new->body = $4.stmts;
$$ = (PLpgSQL_stmt *) new;
}
check_labels($1, $4.end_label, $4.end_label_location);
/* close namespace started in opt_loop_label */
plpgsql_ns_pop();
}
;
for_control : for_variable K_IN
{
int tok = yylex();
int tokloc = yylloc;
if (tok == K_EXECUTE)
{
/* EXECUTE means it's a dynamic FOR loop */
PLpgSQL_stmt_dynfors *new;
PLpgSQL_expr *expr;
int term;
expr = read_sql_expression2(K_LOOP, K_USING,
"LOOP or USING",
&term);
new = palloc0(sizeof(PLpgSQL_stmt_dynfors));
new->cmd_type = PLPGSQL_STMT_DYNFORS;
new->stmtid = ++plpgsql_curr_compile->nstatements;
if ($1.row)
{
new->var = (PLpgSQL_variable *) $1.row;
check_assignable($1.row, @1);
}
else if ($1.scalar)
{
/* convert single scalar to list */
new->var = (PLpgSQL_variable *)
make_scalar_list1($1.name, $1.scalar,
$1.lineno, @1);
/* make_scalar_list1 did check_assignable */
}
else
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("loop variable of loop over rows must be a record variable or list of scalar variables"),
parser_errposition(@1)));
}
new->query = expr;
if (term == K_USING)
{
do
{
expr = read_sql_expression2(',', K_LOOP,
", or LOOP",
&term);
new->params = lappend(new->params, expr);
} while (term == ',');
}
$$ = (PLpgSQL_stmt *) new;
}
else if (tok == T_DATUM &&
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR &&
((PLpgSQL_var *) yylval.wdatum.datum)->datatype->typoid == REFCURSOROID)
{
/* It's FOR var IN cursor */
PLpgSQL_stmt_forc *new;
PLpgSQL_var *cursor = (PLpgSQL_var *) yylval.wdatum.datum;
new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc));
new->cmd_type = PLPGSQL_STMT_FORC;
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->curvar = cursor->dno;
/* Should have had a single variable name */
if ($1.scalar && $1.row)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cursor FOR loop must have only one target variable"),
parser_errposition(@1)));
/* can't use an unbound cursor this way */
if (cursor->cursor_explicit_expr == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cursor FOR loop must use a bound cursor variable"),
parser_errposition(tokloc)));
/* collect cursor's parameters if any */
new->argquery = read_cursor_args(cursor,
K_LOOP,
"LOOP");
/* create loop's private RECORD variable */
new->var = (PLpgSQL_variable *)
plpgsql_build_record($1.name,
$1.lineno,
NULL,
RECORDOID,
true);
$$ = (PLpgSQL_stmt *) new;
}
else
{
PLpgSQL_expr *expr1;
int expr1loc;
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_is_keyword(tok, &yylval,
K_REVERSE, "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(DOT_DOT,
K_LOOP,
0,
"LOOP",
"SELECT ",
true,
false,
true,
&expr1loc,
&tok);
if (tok == DOT_DOT)
{
/* Saw "..", so it must be an integer loop */
PLpgSQL_expr *expr2;
PLpgSQL_expr *expr_by;
PLpgSQL_var *fvar;
PLpgSQL_stmt_fori *new;
/* Check first expression is well-formed */
check_sql_expr(expr1->query, expr1loc, 7);
/* Read and check the second one */
expr2 = read_sql_expression2(K_LOOP, K_BY,
"LOOP",
&tok);
/* Get the BY clause if any */
if (tok == K_BY)
expr_by = read_sql_expression(K_LOOP,
"LOOP");
else
expr_by = NULL;
/* Should have had a single variable name */
if ($1.scalar && $1.row)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("integer FOR loop must have only one target variable"),
parser_errposition(@1)));
/* create loop's private variable */
fvar = (PLpgSQL_var *)
plpgsql_build_variable($1.name,
$1.lineno,
plpgsql_build_datatype(INT4OID,
-1,
InvalidOid,
NULL),
true);
new = palloc0(sizeof(PLpgSQL_stmt_fori));
new->cmd_type = PLPGSQL_STMT_FORI;
new->stmtid = ++plpgsql_curr_compile->nstatements;
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)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot specify REVERSE in query FOR loop"),
parser_errposition(tokloc)));
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, expr1loc, 0);
new = palloc0(sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
new->stmtid = ++plpgsql_curr_compile->nstatements;
if ($1.row)
{
new->var = (PLpgSQL_variable *) $1.row;
check_assignable($1.row, @1);
}
else if ($1.scalar)
{
/* convert single scalar to list */
new->var = (PLpgSQL_variable *)
make_scalar_list1($1.name, $1.scalar,
$1.lineno, @1);
/* make_scalar_list1 did check_assignable */
}
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("loop variable of loop over rows must be a record variable or list of scalar variables"),
parser_errposition(@1)));
}
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_DATUM
{
$$.name = NameOfDatum(&($1));
$$.lineno = plpgsql_location_to_lineno(@1);
if ($1.datum->dtype == PLPGSQL_DTYPE_ROW ||
$1.datum->dtype == PLPGSQL_DTYPE_REC)
{
$$.scalar = NULL;
$$.row = $1.datum;
}
else
{
int tok;
$$.scalar = $1.datum;
$$.row = NULL;
/* check for comma-separated list */
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == ',')
$$.row = (PLpgSQL_datum *)
read_into_scalar_list($$.name,
$$.scalar,
@1);
}
}
| T_WORD
{
int tok;
$$.name = $1.ident;
$$.lineno = plpgsql_location_to_lineno(@1);
$$.scalar = NULL;
$$.row = NULL;
/* check for comma-separated list */
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == ',')
word_is_not_variable(&($1), @1);
}
| T_CWORD
{
/* just to give a better message than "syntax error" */
cword_is_not_variable(&($1), @1);
}
;
stmt_foreach_a : opt_loop_label K_FOREACH for_variable foreach_slice K_IN K_ARRAY expr_until_loop loop_body
{
PLpgSQL_stmt_foreach_a *new;
new = palloc0(sizeof(PLpgSQL_stmt_foreach_a));
new->cmd_type = PLPGSQL_STMT_FOREACH_A;
new->lineno = plpgsql_location_to_lineno(@2);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->label = $1;
new->slice = $4;
new->expr = $7;
new->body = $8.stmts;
if ($3.row)
{
new->varno = $3.row->dno;
check_assignable($3.row, @3);
}
else if ($3.scalar)
{
new->varno = $3.scalar->dno;
check_assignable($3.scalar, @3);
}
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("loop variable of FOREACH must be a known variable or list of variables"),
parser_errposition(@3)));
}
check_labels($1, $8.end_label, $8.end_label_location);
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *) new;
}
;
foreach_slice :
{
$$ = 0;
}
| K_SLICE ICONST
{
$$ = $2;
}
;
stmt_exit : exit_type opt_label opt_exitcond
{
PLpgSQL_stmt_exit *new;
new = palloc0(sizeof(PLpgSQL_stmt_exit));
new->cmd_type = PLPGSQL_STMT_EXIT;
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->is_exit = $1;
new->lineno = plpgsql_location_to_lineno(@1);
new->label = $2;
new->cond = $3;
if ($2)
{
/* We have a label, so verify it exists */
PLpgSQL_nsitem *label;
label = plpgsql_ns_lookup_label(plpgsql_ns_top(), $2);
if (label == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("there is no label \"%s\" "
"attached to any block or loop enclosing this statement",
$2),
parser_errposition(@2)));
/* CONTINUE only allows loop labels */
if (label->itemno != PLPGSQL_LABEL_LOOP && !new->is_exit)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("block label \"%s\" cannot be used in CONTINUE",
$2),
parser_errposition(@2)));
}
else
{
/*
* No label, so make sure there is some loop (an
* unlabelled EXIT does not match a block, so this
* is the same test for both EXIT and CONTINUE)
*/
if (plpgsql_ns_find_nearest_loop(plpgsql_ns_top()) == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
new->is_exit ?
errmsg("EXIT cannot be used outside a loop, unless it has a label") :
errmsg("CONTINUE cannot be used outside a loop"),
parser_errposition(@1)));
}
$$ = (PLpgSQL_stmt *)new;
}
;
exit_type : K_EXIT
{
$$ = true;
}
| K_CONTINUE
{
$$ = false;
}
;
stmt_return : K_RETURN
{
int tok;
tok = yylex();
if (tok == 0)
yyerror("unexpected end of function definition");
if (tok_is_keyword(tok, &yylval,
K_NEXT, "next"))
{
$$ = make_return_next_stmt(@1);
}
else if (tok_is_keyword(tok, &yylval,
K_QUERY, "query"))
{
$$ = make_return_query_stmt(@1);
}
else
{
plpgsql_push_back_token(tok);
$$ = make_return_stmt(@1);
}
}
;
stmt_raise : K_RAISE
{
PLpgSQL_stmt_raise *new;
int tok;
new = palloc(sizeof(PLpgSQL_stmt_raise));
new->cmd_type = PLPGSQL_STMT_RAISE;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->elog_level = ERROR; /* default */
new->condname = NULL;
new->message = NULL;
new->params = NIL;
new->options = NIL;
tok = yylex();
if (tok == 0)
yyerror("unexpected end of function definition");
/*
* We could have just RAISE, meaning to re-throw
* the current error.
*/
if (tok != ';')
{
/*
* First is an optional elog severity level.
*/
if (tok_is_keyword(tok, &yylval,
K_EXCEPTION, "exception"))
{
new->elog_level = ERROR;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_WARNING, "warning"))
{
new->elog_level = WARNING;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_NOTICE, "notice"))
{
new->elog_level = NOTICE;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_INFO, "info"))
{
new->elog_level = INFO;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_LOG, "log"))
{
new->elog_level = LOG;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_DEBUG, "debug"))
{
new->elog_level = DEBUG1;
tok = yylex();
}
if (tok == 0)
yyerror("unexpected end of function definition");
/*
* Next we can have a condition name, or
* equivalently SQLSTATE 'xxxxx', or a string
* literal that is the old-style message format,
* or USING to start the option list immediately.
*/
if (tok == SCONST)
{
/* old style message and parameters */
new->message = yylval.str;
/*
* We expect either a semi-colon, which
* indicates no parameters, or a comma that
* begins the list of parameter expressions,
* or USING to begin the options list.
*/
tok = yylex();
if (tok != ',' && tok != ';' && tok != K_USING)
yyerror("syntax error");
while (tok == ',')
{
PLpgSQL_expr *expr;
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
"SELECT ",
true, true, true,
NULL, &tok);
new->params = lappend(new->params, expr);
}
}
else if (tok != K_USING)
{
/* must be condition name or SQLSTATE */
if (tok_is_keyword(tok, &yylval,
K_SQLSTATE, "sqlstate"))
{
/* next token should be a string literal */
char *sqlstatestr;
if (yylex() != SCONST)
yyerror("syntax error");
sqlstatestr = yylval.str;
if (strlen(sqlstatestr) != 5)
yyerror("invalid SQLSTATE code");
if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
yyerror("invalid SQLSTATE code");
new->condname = sqlstatestr;
}
else
{
if (tok == T_WORD)
new->condname = yylval.word.ident;
else if (plpgsql_token_is_unreserved_keyword(tok))
new->condname = pstrdup(yylval.keyword);
else
yyerror("syntax error");
plpgsql_recognize_err_condition(new->condname,
false);
}
tok = yylex();
if (tok != ';' && tok != K_USING)
yyerror("syntax error");
}
if (tok == K_USING)
new->options = read_raise_options();
}
check_raise_parameters(new);
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_assert : K_ASSERT
{
PLpgSQL_stmt_assert *new;
int tok;
new = palloc(sizeof(PLpgSQL_stmt_assert));
new->cmd_type = PLPGSQL_STMT_ASSERT;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->cond = read_sql_expression2(',', ';',
", or ;",
&tok);
if (tok == ',')
new->message = read_sql_expression(';', ";");
else
new->message = NULL;
$$ = (PLpgSQL_stmt *) new;
}
;
loop_body : proc_sect K_END K_LOOP opt_label ';'
{
$$.stmts = $1;
$$.end_label = $4;
$$.end_label_location = @4;
}
;
/*
* T_WORD+T_CWORD match any initial identifier that is not a known plpgsql
* variable. (The composite case is probably a syntax error, but we'll let
* the core parser decide that.) Normally, we should assume that such a
* word is a SQL statement keyword that isn't also a plpgsql keyword.
* However, if the next token is assignment or '[', it can't be a valid
* SQL statement, and what we're probably looking at is an intended variable
* assignment. Give an appropriate complaint for that, instead of letting
* the core parser throw an unhelpful "syntax error".
*/
stmt_execsql : K_IMPORT
{
$$ = make_execsql_stmt(K_IMPORT, @1);
}
| K_INSERT
{
$$ = make_execsql_stmt(K_INSERT, @1);
}
| T_WORD
{
int tok;
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == '=' || tok == COLON_EQUALS || tok == '[')
word_is_not_variable(&($1), @1);
$$ = make_execsql_stmt(T_WORD, @1);
}
| T_CWORD
{
int tok;
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == '=' || tok == COLON_EQUALS || tok == '[')
cword_is_not_variable(&($1), @1);
$$ = make_execsql_stmt(T_CWORD, @1);
}
;
stmt_dynexecute : K_EXECUTE
{
PLpgSQL_stmt_dynexecute *new;
PLpgSQL_expr *expr;
int endtoken;
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
"SELECT ",
true, true, true,
NULL, &endtoken);
new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->query = expr;
new->into = false;
new->strict = false;
new->target = NULL;
new->params = NIL;
/*
* We loop to allow the INTO and USING clauses to
* appear in either order, since people easily get
* that wrong. This coding also prevents "INTO foo"
* from getting absorbed into a USING expression,
* which is *really* confusing.
*/
for (;;)
{
if (endtoken == K_INTO)
{
if (new->into) /* multiple INTO */
yyerror("syntax error");
new->into = true;
read_into_target(&new->target, &new->strict);
endtoken = yylex();
}
else if (endtoken == K_USING)
{
if (new->params) /* multiple USING */
yyerror("syntax error");
do
{
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
"SELECT ",
true, true, true,
NULL, &endtoken);
new->params = lappend(new->params, expr);
} while (endtoken == ',');
}
else if (endtoken == ';')
break;
else
yyerror("syntax error");
}
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_open : K_OPEN cursor_variable
{
PLpgSQL_stmt_open *new;
int tok;
new = palloc0(sizeof(PLpgSQL_stmt_open));
new->cmd_type = PLPGSQL_STMT_OPEN;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->curvar = $2->dno;
new->cursor_options = CURSOR_OPT_FAST_PLAN;
if ($2->cursor_explicit_expr == NULL)
{
/* be nice if we could use opt_scrollable here */
tok = yylex();
if (tok_is_keyword(tok, &yylval,
K_NO, "no"))
{
tok = yylex();
if (tok_is_keyword(tok, &yylval,
K_SCROLL, "scroll"))
{
new->cursor_options |= CURSOR_OPT_NO_SCROLL;
tok = yylex();
}
}
else if (tok_is_keyword(tok, &yylval,
K_SCROLL, "scroll"))
{
new->cursor_options |= CURSOR_OPT_SCROLL;
tok = yylex();
}
if (tok != K_FOR)
yyerror("syntax error, expected \"FOR\"");
tok = yylex();
if (tok == K_EXECUTE)
{
int endtoken;
new->dynquery =
read_sql_expression2(K_USING, ';',
"USING or ;",
&endtoken);
/* If we found "USING", collect argument(s) */
if (endtoken == K_USING)
{
PLpgSQL_expr *expr;
do
{
expr = read_sql_expression2(',', ';',
", or ;",
&endtoken);
new->params = lappend(new->params,
expr);
} while (endtoken == ',');
}
}
else
{
plpgsql_push_back_token(tok);
new->query = read_sql_stmt("");
}
}
else
{
/* predefined cursor query, so read args */
new->argquery = read_cursor_args($2, ';', ";");
}
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_fetch : K_FETCH opt_fetch_direction cursor_variable K_INTO
{
PLpgSQL_stmt_fetch *fetch = $2;
PLpgSQL_variable *target;
/* We have already parsed everything through the INTO keyword */
read_into_target(&target, NULL);
if (yylex() != ';')
yyerror("syntax error");
/*
* We don't allow multiple rows in PL/pgSQL's FETCH
* statement, only in MOVE.
*/
if (fetch->returns_multiple_rows)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("FETCH statement cannot return multiple rows"),
parser_errposition(@1)));
fetch->lineno = plpgsql_location_to_lineno(@1);
fetch->target = target;
fetch->curvar = $3->dno;
fetch->is_move = false;
$$ = (PLpgSQL_stmt *)fetch;
}
;
stmt_move : K_MOVE opt_fetch_direction cursor_variable ';'
{
PLpgSQL_stmt_fetch *fetch = $2;
fetch->lineno = plpgsql_location_to_lineno(@1);
fetch->curvar = $3->dno;
fetch->is_move = true;
$$ = (PLpgSQL_stmt *)fetch;
}
;
opt_fetch_direction :
{
$$ = read_fetch_direction();
}
;
stmt_close : K_CLOSE cursor_variable ';'
{
PLpgSQL_stmt_close *new;
new = palloc(sizeof(PLpgSQL_stmt_close));
new->cmd_type = PLPGSQL_STMT_CLOSE;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->curvar = $2->dno;
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_null : K_NULL ';'
{
/* We do not bother building a node for NULL */
$$ = NULL;
}
;
stmt_commit : K_COMMIT opt_transaction_chain ';'
{
PLpgSQL_stmt_commit *new;
new = palloc(sizeof(PLpgSQL_stmt_commit));
new->cmd_type = PLPGSQL_STMT_COMMIT;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->chain = $2;
$$ = (PLpgSQL_stmt *)new;
}
;
stmt_rollback : K_ROLLBACK opt_transaction_chain ';'
{
PLpgSQL_stmt_rollback *new;
new = palloc(sizeof(PLpgSQL_stmt_rollback));
new->cmd_type = PLPGSQL_STMT_ROLLBACK;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->chain = $2;
$$ = (PLpgSQL_stmt *)new;
}
;
opt_transaction_chain:
K_AND K_CHAIN { $$ = true; }
| K_AND K_NO K_CHAIN { $$ = false; }
| /* EMPTY */ { $$ = false; }
;
stmt_set : K_SET
{
PLpgSQL_stmt_set *new;
new = palloc0(sizeof(PLpgSQL_stmt_set));
new->cmd_type = PLPGSQL_STMT_SET;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->expr = read_sql_stmt("SET ");
$$ = (PLpgSQL_stmt *)new;
}
| K_RESET
{
PLpgSQL_stmt_set *new;
new = palloc0(sizeof(PLpgSQL_stmt_set));
new->cmd_type = PLPGSQL_STMT_SET;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->expr = read_sql_stmt("RESET ");
$$ = (PLpgSQL_stmt *)new;
}
;
cursor_variable : T_DATUM
{
/*
* In principle we should support a cursor_variable
* that is an array element, but for now we don't, so
* just throw an error if next token is '['.
*/
if ($1.datum->dtype != PLPGSQL_DTYPE_VAR ||
plpgsql_peek() == '[')
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cursor variable must be a simple variable"),
parser_errposition(@1)));
if (((PLpgSQL_var *) $1.datum)->datatype->typoid != REFCURSOROID)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("variable \"%s\" must be of type cursor or refcursor",
((PLpgSQL_var *) $1.datum)->refname),
parser_errposition(@1)));
$$ = (PLpgSQL_var *) $1.datum;
}
| T_WORD
{
/* just to give a better message than "syntax error" */
word_is_not_variable(&($1), @1);
}
| T_CWORD
{
/* just to give a better message than "syntax error" */
cword_is_not_variable(&($1), @1);
}
;
exception_sect :
{ $$ = NULL; }
| K_EXCEPTION
{
/*
* We use a mid-rule action to add these
* special variables to the namespace before
* parsing the WHEN clauses themselves. The
* scope of the names extends to the end of the
* current block.
*/
int lineno = plpgsql_location_to_lineno(@1);
PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
PLpgSQL_variable *var;
var = plpgsql_build_variable("sqlstate", lineno,
plpgsql_build_datatype(TEXTOID,
-1,
plpgsql_curr_compile->fn_input_collation,
NULL),
true);
var->isconst = true;
new->sqlstate_varno = var->dno;
var = plpgsql_build_variable("sqlerrm", lineno,
plpgsql_build_datatype(TEXTOID,
-1,
plpgsql_curr_compile->fn_input_collation,
NULL),
true);
var->isconst = true;
new->sqlerrm_varno = var->dno;
$<exception_block>$ = new;
}
proc_exceptions
{
PLpgSQL_exception_block *new = $<exception_block>2;
new->exc_list = $3;
$$ = new;
}
;
proc_exceptions : proc_exceptions proc_exception
{
$$ = lappend($1, $2);
}
| proc_exception
{
$$ = list_make1($1);
}
;
proc_exception : K_WHEN proc_conditions K_THEN proc_sect
{
PLpgSQL_exception *new;
new = palloc0(sizeof(PLpgSQL_exception));
new->lineno = plpgsql_location_to_lineno(@1);
new->conditions = $2;
new->action = $4;
$$ = new;
}
;
proc_conditions : proc_conditions K_OR proc_condition
{
PLpgSQL_condition *old;
for (old = $1; old->next != NULL; old = old->next)
/* skip */ ;
old->next = $3;
$$ = $1;
}
| proc_condition
{
$$ = $1;
}
;
proc_condition : any_identifier
{
if (strcmp($1, "sqlstate") != 0)
{
$$ = plpgsql_parse_err_condition($1);
}
else
{
PLpgSQL_condition *new;
char *sqlstatestr;
/* next token should be a string literal */
if (yylex() != SCONST)
yyerror("syntax error");
sqlstatestr = yylval.str;
if (strlen(sqlstatestr) != 5)
yyerror("invalid SQLSTATE code");
if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
yyerror("invalid SQLSTATE code");
new = palloc(sizeof(PLpgSQL_condition));
new->sqlerrstate =
MAKE_SQLSTATE(sqlstatestr[0],
sqlstatestr[1],
sqlstatestr[2],
sqlstatestr[3],
sqlstatestr[4]);
new->condname = sqlstatestr;
new->next = NULL;
$$ = new;
}
}
;
expr_until_semi :
{ $$ = read_sql_expression(';', ";"); }
;
expr_until_rightbracket :
{ $$ = read_sql_expression(']', "]"); }
;
expr_until_then :
{ $$ = read_sql_expression(K_THEN, "THEN"); }
;
expr_until_loop :
{ $$ = read_sql_expression(K_LOOP, "LOOP"); }
;
opt_block_label :
{
plpgsql_ns_push(NULL, PLPGSQL_LABEL_BLOCK);
$$ = NULL;
}
| LESS_LESS any_identifier GREATER_GREATER
{
plpgsql_ns_push($2, PLPGSQL_LABEL_BLOCK);
$$ = $2;
}
;
opt_loop_label :
{
plpgsql_ns_push(NULL, PLPGSQL_LABEL_LOOP);
$$ = NULL;
}
| LESS_LESS any_identifier GREATER_GREATER
{
plpgsql_ns_push($2, PLPGSQL_LABEL_LOOP);
$$ = $2;
}
;
opt_label :
{
$$ = NULL;
}
| any_identifier
{
/* label validity will be checked by outer production */
$$ = $1;
}
;
opt_exitcond : ';'
{ $$ = NULL; }
| K_WHEN expr_until_semi
{ $$ = $2; }
;
/*
* need to allow DATUM because scanner will have tried to resolve as variable
*/
any_identifier : T_WORD
{
$$ = $1.ident;
}
| unreserved_keyword
{
$$ = pstrdup($1);
}
| T_DATUM
{
if ($1.ident == NULL) /* composite name not OK */
yyerror("syntax error");
$$ = $1.ident;
}
;
unreserved_keyword :
K_ABSOLUTE
| K_ALIAS
| K_AND
| K_ARRAY
| K_ASSERT
| K_BACKWARD
| K_CALL
| K_CHAIN
| K_CLOSE
| K_COLLATE
| K_COLUMN
| K_COLUMN_NAME
| K_COMMIT
| K_CONSTANT
| K_CONSTRAINT
| K_CONSTRAINT_NAME
| K_CONTINUE
| K_CURRENT
| K_CURSOR
| K_DATATYPE
| K_DEBUG
| K_DEFAULT
| K_DETAIL
| K_DIAGNOSTICS
| K_DO
| K_DUMP
| K_ELSIF
| K_ERRCODE
| K_ERROR
| K_EXCEPTION
| K_EXIT
| K_FETCH
| K_FIRST
| K_FORWARD
| K_GET
| K_HINT
| K_IMPORT
| K_INFO
| K_INSERT
| K_IS
| K_LAST
| K_LOG
| K_MESSAGE
| K_MESSAGE_TEXT
| K_MOVE
| K_NEXT
| K_NO
| K_NOTICE
| K_OPEN
| K_OPTION
| K_PERFORM
| K_PG_CONTEXT
| K_PG_DATATYPE_NAME
| K_PG_EXCEPTION_CONTEXT
| K_PG_EXCEPTION_DETAIL
| K_PG_EXCEPTION_HINT
| K_PRINT_STRICT_PARAMS
| K_PRIOR
| K_QUERY
| K_RAISE
| K_RELATIVE
| K_RESET
| K_RETURN
| K_RETURNED_SQLSTATE
| K_REVERSE
| K_ROLLBACK
| K_ROW_COUNT
| K_ROWTYPE
| K_SCHEMA
| K_SCHEMA_NAME
| K_SCROLL
| K_SET
| K_SLICE
| K_SQLSTATE
| K_STACKED
| K_TABLE
| K_TABLE_NAME
| K_TYPE
| K_USE_COLUMN
| K_USE_VARIABLE
| K_VARIABLE_CONFLICT
| K_WARNING
;
%%
/*
* Check whether a token represents an "unreserved keyword".
* We have various places where we want to recognize a keyword in preference
* to a variable name, but not reserve that keyword in other contexts.
* Hence, this kluge.
*/
static bool
tok_is_keyword(int token, union YYSTYPE *lval,
int kw_token, const char *kw_str)
{
if (token == kw_token)
{
/* Normal case, was recognized by scanner (no conflicting variable) */
return true;
}
else if (token == T_DATUM)
{
/*
* It's a variable, so recheck the string name. Note we will not
* match composite names (hence an unreserved word followed by "."
* will not be recognized).
*/
if (!lval->wdatum.quoted && lval->wdatum.ident != NULL &&
strcmp(lval->wdatum.ident, kw_str) == 0)
return true;
}
return false; /* not the keyword */
}
/*
* Convenience routine to complain when we expected T_DATUM and got T_WORD,
* ie, unrecognized variable.
*/
static void
word_is_not_variable(PLword *word, int location)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a known variable",
word->ident),
parser_errposition(location)));
}
/* Same, for a CWORD */
static void
cword_is_not_variable(PLcword *cword, int location)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a known variable",
NameListToString(cword->idents)),
parser_errposition(location)));
}
/*
* Convenience routine to complain when we expected T_DATUM and got
* something else. "tok" must be the current token, since we also
* look at yylval and yylloc.
*/
static void
current_token_is_not_variable(int tok)
{
if (tok == T_WORD)
word_is_not_variable(&(yylval.word), yylloc);
else if (tok == T_CWORD)
cword_is_not_variable(&(yylval.cword), yylloc);
else
yyerror("syntax error");
}
/* Convenience routine to read an expression with one possible terminator */
static PLpgSQL_expr *
read_sql_expression(int until, const char *expected)
{
return read_sql_construct(until, 0, 0, expected,
"SELECT ", true, true, true, NULL, NULL);
}
/* Convenience routine to read an expression with two possible terminators */
static PLpgSQL_expr *
read_sql_expression2(int until, int until2, const char *expected,
int *endtoken)
{
return read_sql_construct(until, until2, 0, expected,
"SELECT ", true, true, true, NULL, endtoken);
}
/* Convenience routine to read a SQL statement that must end with ';' */
static PLpgSQL_expr *
read_sql_stmt(const char *sqlstart)
{
return read_sql_construct(';', 0, 0, ";",
sqlstart, false, true, true, NULL, 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)
* until3: token code for another 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)
* trim: trim trailing whitespace
* startloc: if not NULL, location of first token is stored at *startloc
* endtoken: if not NULL, ending token is stored at *endtoken
* (this is only interesting if until2 or until3 isn't zero)
*/
static PLpgSQL_expr *
read_sql_construct(int until,
int until2,
int until3,
const char *expected,
const char *sqlstart,
bool isexpression,
bool valid_sql,
bool trim,
int *startloc,
int *endtoken)
{
int tok;
StringInfoData ds;
IdentifierLookup save_IdentifierLookup;
int startlocation = -1;
int parenlevel = 0;
PLpgSQL_expr *expr;
initStringInfo(&ds);
appendStringInfoString(&ds, sqlstart);
/* special lookup mode for identifiers within the SQL text */
save_IdentifierLookup = plpgsql_IdentifierLookup;
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
for (;;)
{
tok = yylex();
if (startlocation < 0) /* remember loc of first token */
startlocation = yylloc;
if (tok == until && parenlevel == 0)
break;
if (tok == until2 && parenlevel == 0)
break;
if (tok == until3 && 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");
if (isexpression)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("missing \"%s\" at end of SQL expression",
expected),
parser_errposition(yylloc)));
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("missing \"%s\" at end of SQL statement",
expected),
parser_errposition(yylloc)));
}
}
plpgsql_IdentifierLookup = save_IdentifierLookup;
if (startloc)
*startloc = startlocation;
if (endtoken)
*endtoken = tok;
/* give helpful complaint about empty input */
if (startlocation >= yylloc)
{
if (isexpression)
yyerror("missing expression");
else
yyerror("missing SQL statement");
}
plpgsql_append_source_text(&ds, startlocation, yylloc);
/* trim any trailing whitespace, for neatness */
if (trim)
{
while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
ds.data[--ds.len] = '\0';
}
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
if (valid_sql)
check_sql_expr(expr->query, startlocation, strlen(sqlstart));
return expr;
}
static PLpgSQL_type *
read_datatype(int tok)
{
StringInfoData ds;
char *type_name;
int startlocation;
PLpgSQL_type *result;
int parenlevel = 0;
/* Should only be called while parsing DECLARE sections */
Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE);
/* Often there will be a lookahead token, but if not, get one */
if (tok == YYEMPTY)
tok = yylex();
startlocation = yylloc;
/*
* If we have a simple or composite identifier, check for %TYPE
* and %ROWTYPE constructs.
*/
if (tok == T_WORD)
{
char *dtname = yylval.word.ident;
tok = yylex();
if (tok == '%')
{
tok = yylex();
if (tok_is_keyword(tok, &yylval,
K_TYPE, "type"))
{
result = plpgsql_parse_wordtype(dtname);
if (result)
return result;
}
else if (tok_is_keyword(tok, &yylval,
K_ROWTYPE, "rowtype"))
{
result = plpgsql_parse_wordrowtype(dtname);
if (result)
return result;
}
}
}
else if (plpgsql_token_is_unreserved_keyword(tok))
{
char *dtname = pstrdup(yylval.keyword);
tok = yylex();
if (tok == '%')
{
tok = yylex();
if (tok_is_keyword(tok, &yylval,
K_TYPE, "type"))
{
result = plpgsql_parse_wordtype(dtname);
if (result)
return result;
}
else if (tok_is_keyword(tok, &yylval,
K_ROWTYPE, "rowtype"))
{
result = plpgsql_parse_wordrowtype(dtname);
if (result)
return result;
}
}
}
else if (tok == T_CWORD)
{
List *dtnames = yylval.cword.idents;
tok = yylex();
if (tok == '%')
{
tok = yylex();
if (tok_is_keyword(tok, &yylval,
K_TYPE, "type"))
{
result = plpgsql_parse_cwordtype(dtnames);
if (result)
return result;
}
else if (tok_is_keyword(tok, &yylval,
K_ROWTYPE, "rowtype"))
{
result = plpgsql_parse_cwordrowtype(dtnames);
if (result)
return result;
}
}
}
while (tok != ';')
{
if (tok == 0)
{
if (parenlevel != 0)
yyerror("mismatched parentheses");
else
yyerror("incomplete data type declaration");
}
/* Possible followers for datatype in a declaration */
if (tok == K_COLLATE || tok == K_NOT ||
tok == '=' || tok == COLON_EQUALS || 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--;
tok = yylex();
}
/* set up ds to contain complete typename text */
initStringInfo(&ds);
plpgsql_append_source_text(&ds, startlocation, yylloc);
type_name = ds.data;
if (type_name[0] == '\0')
yyerror("missing data type declaration");
result = parse_datatype(type_name, startlocation);
pfree(ds.data);
plpgsql_push_back_token(tok);
return result;
}
static PLpgSQL_stmt *
make_execsql_stmt(int firsttoken, int location)
{
StringInfoData ds;
IdentifierLookup save_IdentifierLookup;
PLpgSQL_stmt_execsql *execsql;
PLpgSQL_expr *expr;
PLpgSQL_variable *target = NULL;
int tok;
int prev_tok;
bool have_into = false;
bool have_strict = false;
int into_start_loc = -1;
int into_end_loc = -1;
initStringInfo(&ds);
/* special lookup mode for identifiers within the SQL text */
save_IdentifierLookup = plpgsql_IdentifierLookup;
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
/*
* Scan to the end of the SQL command. Identify any INTO-variables
* clause lurking within it, and parse that via read_into_target().
*
* Because INTO is sometimes used in the main SQL grammar, we have to be
* careful not to take any such usage of INTO as a PL/pgSQL INTO clause.
* There are currently three such cases:
*
* 1. SELECT ... INTO. We don't care, we just override that with the
* PL/pgSQL definition.
*
* 2. INSERT INTO. This is relatively easy to recognize since the words
* must appear adjacently; but we can't assume INSERT starts the command,
* because it can appear in CREATE RULE or WITH. Unfortunately, INSERT is
* *not* fully reserved, so that means there is a chance of a false match;
* but it's not very likely.
*
* 3. IMPORT FOREIGN SCHEMA ... INTO. This is not allowed in CREATE RULE
* or WITH, so we just check for IMPORT as the command's first token.
* (If IMPORT FOREIGN SCHEMA returned data someone might wish to capture
* with an INTO-variables clause, we'd have to work much harder here.)
*
* Fortunately, INTO is a fully reserved word in the main grammar, so
* at least we need not worry about it appearing as an identifier.
*
* Any future additional uses of INTO in the main grammar will doubtless
* break this logic again ... beware!
*/
tok = firsttoken;
for (;;)
{
prev_tok = tok;
tok = yylex();
if (have_into && into_end_loc < 0)
into_end_loc = yylloc; /* token after the INTO part */
if (tok == ';')
break;
if (tok == 0)
yyerror("unexpected end of function definition");
if (tok == K_INTO)
{
if (prev_tok == K_INSERT)
continue; /* INSERT INTO is not an INTO-target */
if (firsttoken == K_IMPORT)
continue; /* IMPORT ... INTO is not an INTO-target */
if (have_into)
yyerror("INTO specified more than once");
have_into = true;
into_start_loc = yylloc;
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
read_into_target(&target, &have_strict);
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
}
}
plpgsql_IdentifierLookup = save_IdentifierLookup;
if (have_into)
{
/*
* Insert an appropriate number of spaces corresponding to the
* INTO text, so that locations within the redacted SQL statement
* still line up with those in the original source text.
*/
plpgsql_append_source_text(&ds, location, into_start_loc);
appendStringInfoSpaces(&ds, into_end_loc - into_start_loc);
plpgsql_append_source_text(&ds, into_end_loc, yylloc);
}
else
plpgsql_append_source_text(&ds, location, yylloc);
/* trim any trailing whitespace, for neatness */
while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
ds.data[--ds.len] = '\0';
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
check_sql_expr(expr->query, location, 0);
execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
execsql->lineno = plpgsql_location_to_lineno(location);
execsql->stmtid = ++plpgsql_curr_compile->nstatements;
execsql->sqlstmt = expr;
execsql->into = have_into;
execsql->strict = have_strict;
execsql->target = target;
return (PLpgSQL_stmt *) execsql;
}
/*
* Read FETCH or MOVE direction clause (everything through FROM/IN).
*/
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;
fetch->stmtid = ++plpgsql_curr_compile->nstatements;
/* set direction defaults: */
fetch->direction = FETCH_FORWARD;
fetch->how_many = 1;
fetch->expr = NULL;
fetch->returns_multiple_rows = false;
tok = yylex();
if (tok == 0)
yyerror("unexpected end of function definition");
if (tok_is_keyword(tok, &yylval,
K_NEXT, "next"))
{
/* use defaults */
}
else if (tok_is_keyword(tok, &yylval,
K_PRIOR, "prior"))
{
fetch->direction = FETCH_BACKWARD;
}
else if (tok_is_keyword(tok, &yylval,
K_FIRST, "first"))
{
fetch->direction = FETCH_ABSOLUTE;
}
else if (tok_is_keyword(tok, &yylval,
K_LAST, "last"))
{
fetch->direction = FETCH_ABSOLUTE;
fetch->how_many = -1;
}
else if (tok_is_keyword(tok, &yylval,
K_ABSOLUTE, "absolute"))
{
fetch->direction = FETCH_ABSOLUTE;
fetch->expr = read_sql_expression2(K_FROM, K_IN,
"FROM or IN",
NULL);
check_FROM = false;
}
else if (tok_is_keyword(tok, &yylval,
K_RELATIVE, "relative"))
{
fetch->direction = FETCH_RELATIVE;
fetch->expr = read_sql_expression2(K_FROM, K_IN,
"FROM or IN",
NULL);
check_FROM = false;
}
else if (tok_is_keyword(tok, &yylval,
K_ALL, "all"))
{
fetch->how_many = FETCH_ALL;
fetch->returns_multiple_rows = true;
}
else if (tok_is_keyword(tok, &yylval,
K_FORWARD, "forward"))
{
complete_direction(fetch, &check_FROM);
}
else if (tok_is_keyword(tok, &yylval,
K_BACKWARD, "backward"))
{
fetch->direction = FETCH_BACKWARD;
complete_direction(fetch, &check_FROM);
}
else if (tok == K_FROM || tok == K_IN)
{
/* empty direction */
check_FROM = false;
}
else if (tok == T_DATUM)
{
/* Assume there's no direction clause and tok is a cursor name */
plpgsql_push_back_token(tok);
check_FROM = false;
}
else
{
/*
* 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 variable.
* Perhaps this can be improved someday, but it's hardly worth a
* lot of work.
*/
plpgsql_push_back_token(tok);
fetch->expr = read_sql_expression2(K_FROM, K_IN,
"FROM or IN",
NULL);
fetch->returns_multiple_rows = true;
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;
}
/*
* Process remainder of FETCH/MOVE direction after FORWARD or BACKWARD.
* Allows these cases:
* FORWARD expr, FORWARD ALL, FORWARD
* BACKWARD expr, BACKWARD ALL, BACKWARD
*/
static void
complete_direction(PLpgSQL_stmt_fetch *fetch, bool *check_FROM)
{
int tok;
tok = yylex();
if (tok == 0)
yyerror("unexpected end of function definition");
if (tok == K_FROM || tok == K_IN)
{
*check_FROM = false;
return;
}
if (tok == K_ALL)
{
fetch->how_many = FETCH_ALL;
fetch->returns_multiple_rows = true;
*check_FROM = true;
return;
}
plpgsql_push_back_token(tok);
fetch->expr = read_sql_expression2(K_FROM, K_IN,
"FROM or IN",
NULL);
fetch->returns_multiple_rows = true;
*check_FROM = false;
}
static PLpgSQL_stmt *
make_return_stmt(int location)
{
PLpgSQL_stmt_return *new;
new = palloc0(sizeof(PLpgSQL_stmt_return));
new->cmd_type = PLPGSQL_STMT_RETURN;
new->lineno = plpgsql_location_to_lineno(location);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->expr = NULL;
new->retvarno = -1;
if (plpgsql_curr_compile->fn_retset)
{
if (yylex() != ';')
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN cannot have a parameter in function returning set"),
errhint("Use RETURN NEXT or RETURN QUERY."),
parser_errposition(yylloc)));
}
else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
{
if (yylex() != ';')
{
if (plpgsql_curr_compile->fn_prokind == PROKIND_PROCEDURE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RETURN cannot have a parameter in a procedure"),
parser_errposition(yylloc)));
else
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN cannot have a parameter in function returning void"),
parser_errposition(yylloc)));
}
}
else if (plpgsql_curr_compile->out_param_varno >= 0)
{
if (yylex() != ';')
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN cannot have a parameter in function with OUT parameters"),
parser_errposition(yylloc)));
new->retvarno = plpgsql_curr_compile->out_param_varno;
}
else
{
/*
* We want to special-case simple variable references for efficiency.
* So peek ahead to see if that's what we have.
*/
int tok = yylex();
if (tok == T_DATUM && plpgsql_peek() == ';' &&
(yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
{
new->retvarno = yylval.wdatum.datum->dno;
/* eat the semicolon token that we only peeked at above */
tok = yylex();
Assert(tok == ';');
}
else
{
/*
* Not (just) a variable name, so treat as expression.
*
* Note that a well-formed expression is _required_ here;
* anything else is a compile-time error.
*/
plpgsql_push_back_token(tok);
new->expr = read_sql_expression(';', ";");
}
}
return (PLpgSQL_stmt *) new;
}
static PLpgSQL_stmt *
make_return_next_stmt(int location)
{
PLpgSQL_stmt_return_next *new;
if (!plpgsql_curr_compile->fn_retset)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot use RETURN NEXT in a non-SETOF function"),
parser_errposition(location)));
new = palloc0(sizeof(PLpgSQL_stmt_return_next));
new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
new->lineno = plpgsql_location_to_lineno(location);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->expr = NULL;
new->retvarno = -1;
if (plpgsql_curr_compile->out_param_varno >= 0)
{
if (yylex() != ';')
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN NEXT cannot have a parameter in function with OUT parameters"),
parser_errposition(yylloc)));
new->retvarno = plpgsql_curr_compile->out_param_varno;
}
else
{
/*
* We want to special-case simple variable references for efficiency.
* So peek ahead to see if that's what we have.
*/
int tok = yylex();
if (tok == T_DATUM && plpgsql_peek() == ';' &&
(yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
{
new->retvarno = yylval.wdatum.datum->dno;
/* eat the semicolon token that we only peeked at above */
tok = yylex();
Assert(tok == ';');
}
else
{
/*
* Not (just) a variable name, so treat as expression.
*
* Note that a well-formed expression is _required_ here;
* anything else is a compile-time error.
*/
plpgsql_push_back_token(tok);
new->expr = read_sql_expression(';', ";");
}
}
return (PLpgSQL_stmt *) new;
}
static PLpgSQL_stmt *
make_return_query_stmt(int location)
{
PLpgSQL_stmt_return_query *new;
int tok;
if (!plpgsql_curr_compile->fn_retset)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot use RETURN QUERY in a non-SETOF function"),
parser_errposition(location)));
new = palloc0(sizeof(PLpgSQL_stmt_return_query));
new->cmd_type = PLPGSQL_STMT_RETURN_QUERY;
new->lineno = plpgsql_location_to_lineno(location);
new->stmtid = ++plpgsql_curr_compile->nstatements;
/* check for RETURN QUERY EXECUTE */
if ((tok = yylex()) != K_EXECUTE)
{
/* ordinary static query */
plpgsql_push_back_token(tok);
new->query = read_sql_stmt("");
}
else
{
/* dynamic SQL */
int term;
new->dynquery = read_sql_expression2(';', K_USING, "; or USING",
&term);
if (term == K_USING)
{
do
{
PLpgSQL_expr *expr;
expr = read_sql_expression2(',', ';', ", or ;", &term);
new->params = lappend(new->params, expr);
} while (term == ',');
}
}
return (PLpgSQL_stmt *) new;
}
/* convenience routine to fetch the name of a T_DATUM */
static char *
NameOfDatum(PLwdatum *wdatum)
{
if (wdatum->ident)
return wdatum->ident;
Assert(wdatum->idents != NIL);
return NameListToString(wdatum->idents);
}
static void
check_assignable(PLpgSQL_datum *datum, int location)
{
switch (datum->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
case PLPGSQL_DTYPE_REC:
if (((PLpgSQL_variable *) datum)->isconst)
ereport(ERROR,
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
errmsg("variable \"%s\" is declared CONSTANT",
((PLpgSQL_variable *) datum)->refname),
parser_errposition(location)));
break;
case PLPGSQL_DTYPE_ROW:
/* always assignable; member vars were checked at compile time */
break;
case PLPGSQL_DTYPE_RECFIELD:
/* assignable if parent record is */
check_assignable(plpgsql_Datums[((PLpgSQL_recfield *) datum)->recparentno],
location);
break;
case PLPGSQL_DTYPE_ARRAYELEM:
/* assignable if parent array is */
check_assignable(plpgsql_Datums[((PLpgSQL_arrayelem *) datum)->arrayparentno],
location);
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_variable **target, bool *strict)
{
int tok;
/* Set default results */
*target = NULL;
if (strict)
*strict = false;
tok = yylex();
if (strict && tok == K_STRICT)
{
*strict = true;
tok = yylex();
}
/*
* Currently, a row or record variable can be the single INTO target,
* but not a member of a multi-target list. So we throw error if there
* is a comma after it, because that probably means the user tried to
* write a multi-target list. If this ever gets generalized, we should
* probably refactor read_into_scalar_list so it handles all cases.
*/
switch (tok)
{
case T_DATUM:
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
{
check_assignable(yylval.wdatum.datum, yylloc);
*target = (PLpgSQL_variable *) yylval.wdatum.datum;
if ((tok = yylex()) == ',')
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("record variable cannot be part of multiple-item INTO list"),
parser_errposition(yylloc)));
plpgsql_push_back_token(tok);
}
else
{
*target = (PLpgSQL_variable *)
read_into_scalar_list(NameOfDatum(&(yylval.wdatum)),
yylval.wdatum.datum, yylloc);
}
break;
default:
/* just to give a better message than "syntax error" */
current_token_is_not_variable(tok);
}
}
/*
* 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(char *initial_name,
PLpgSQL_datum *initial_datum,
int initial_location)
{
int nfields;
char *fieldnames[1024];
int varnos[1024];
PLpgSQL_row *row;
int tok;
check_assignable(initial_datum, initial_location);
fieldnames[0] = initial_name;
varnos[0] = initial_datum->dno;
nfields = 1;
while ((tok = yylex()) == ',')
{
/* Check for array overflow */
if (nfields >= 1024)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many INTO variables specified"),
parser_errposition(yylloc)));
tok = yylex();
switch (tok)
{
case T_DATUM:
check_assignable(yylval.wdatum.datum, yylloc);
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
NameOfDatum(&(yylval.wdatum))),
parser_errposition(yylloc)));
fieldnames[nfields] = NameOfDatum(&(yylval.wdatum));
varnos[nfields++] = yylval.wdatum.datum->dno;
break;
default:
/* just to give a better message than "syntax error" */
current_token_is_not_variable(tok);
}
}
/*
* We read an extra, non-comma token from yylex(), so push it
* back onto the input stream
*/
plpgsql_push_back_token(tok);
row = palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = "(unnamed row)";
row->lineno = plpgsql_location_to_lineno(initial_location);
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.
*
* Note: lineno could be computed from location, but since callers
* have it at hand already, we may as well pass it in.
*/
static PLpgSQL_row *
make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location)
{
PLpgSQL_row *row;
check_assignable(initial_datum, location);
row = palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = "(unnamed row)";
row->lineno = lineno;
row->rowtupdesc = NULL;
row->nfields = 1;
row->fieldnames = palloc(sizeof(char *));
row->varnos = palloc(sizeof(int));
row->fieldnames[0] = 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.
*
* It is assumed that "stmt" represents a copy of the function source text
* beginning at offset "location", with leader text of length "leaderlen"
* (typically "SELECT ") prefixed to the source text. We use this assumption
* to transpose any error cursor position back to the function source text.
* If no error cursor is provided, we'll just point at "location".
*/
static void
check_sql_expr(const char *stmt, int location, int leaderlen)
{
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
MemoryContext oldCxt;
if (!plpgsql_check_syntax)
return;
cbarg.location = location;
cbarg.leaderlen = leaderlen;
syntax_errcontext.callback = plpgsql_sql_error_callback;
syntax_errcontext.arg = &cbarg;
syntax_errcontext.previous = error_context_stack;
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
(void) raw_parser(stmt);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
error_context_stack = syntax_errcontext.previous;
}
static void
plpgsql_sql_error_callback(void *arg)
{
sql_error_callback_arg *cbarg = (sql_error_callback_arg *) arg;
int errpos;
/*
* First, set up internalerrposition to point to the start of the
* statement text within the function text. Note this converts
* location (a byte offset) to a character number.
*/
parser_errposition(cbarg->location);
/*
* If the core parser provided an error position, transpose it.
* Note we are dealing with 1-based character numbers at this point.
*/
errpos = geterrposition();
if (errpos > cbarg->leaderlen)
{
int myerrpos = getinternalerrposition();
if (myerrpos > 0) /* safety check */
internalerrposition(myerrpos + errpos - cbarg->leaderlen - 1);
}
/* In any case, flush errposition --- we want internalerrposition only */
errposition(0);
}
/*
* Parse a SQL datatype name and produce a PLpgSQL_type structure.
*
* The heavy lifting is done elsewhere. Here we are only concerned
* with setting up an errcontext link that will let us give an error
* cursor pointing into the plpgsql function source, if necessary.
* This is handled the same as in check_sql_expr(), and we likewise
* expect that the given string is a copy from the source text.
*/
static PLpgSQL_type *
parse_datatype(const char *string, int location)
{
TypeName *typeName;
Oid type_id;
int32 typmod;
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
cbarg.location = location;
cbarg.leaderlen = 0;
syntax_errcontext.callback = plpgsql_sql_error_callback;
syntax_errcontext.arg = &cbarg;
syntax_errcontext.previous = error_context_stack;
error_context_stack = &syntax_errcontext;
/* Let the main parser try to parse it under standard SQL rules */
typeName = typeStringToTypeName(string);
typenameTypeIdAndMod(NULL, typeName, &type_id, &typmod);
/* Restore former ereport callback */
error_context_stack = syntax_errcontext.previous;
/* Okay, build a PLpgSQL_type data structure for it */
return plpgsql_build_datatype(type_id, typmod,
plpgsql_curr_compile->fn_input_collation,
typeName);
}
/*
* Check block starting and ending labels match.
*/
static void
check_labels(const char *start_label, const char *end_label, int end_location)
{
if (end_label)
{
if (!start_label)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("end label \"%s\" specified for unlabelled block",
end_label),
parser_errposition(end_location)));
if (strcmp(start_label, end_label) != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("end label \"%s\" differs from block's label \"%s\"",
end_label, start_label),
parser_errposition(end_location)));
}
}
/*
* Read the arguments (if any) for a cursor, followed by the until token
*
* If cursor has no args, just swallow the until token and return NULL.
* If it does have args, we expect to see "( arg [, arg ...] )" followed
* by the until token, where arg may be a plain expression, or a named
* parameter assignment of the form argname := expr. Consume all that and
* return a SELECT query that evaluates the expression(s) (without the outer
* parens).
*/
static PLpgSQL_expr *
read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
{
PLpgSQL_expr *expr;
PLpgSQL_row *row;
int tok;
int argc;
char **argv;
StringInfoData ds;
char *sqlstart = "SELECT ";
bool any_named = false;
tok = yylex();
if (cursor->cursor_explicit_argrow < 0)
{
/* No arguments expected */
if (tok == '(')
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cursor \"%s\" has no arguments",
cursor->refname),
parser_errposition(yylloc)));
if (tok != until)
yyerror("syntax error");
return NULL;
}
/* Else better provide arguments */
if (tok != '(')
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cursor \"%s\" has arguments",
cursor->refname),
parser_errposition(yylloc)));
/*
* Read the arguments, one by one.
*/
row = (PLpgSQL_row *) plpgsql_Datums[cursor->cursor_explicit_argrow];
argv = (char **) palloc0(row->nfields * sizeof(char *));
for (argc = 0; argc < row->nfields; argc++)
{
PLpgSQL_expr *item;
int endtoken;
int argpos;
int tok1,
tok2;
int arglocation;
/* Check if it's a named parameter: "param := value" */
plpgsql_peek2(&tok1, &tok2, &arglocation, NULL);
if (tok1 == IDENT && tok2 == COLON_EQUALS)
{
char *argname;
IdentifierLookup save_IdentifierLookup;
/* Read the argument name, ignoring any matching variable */
save_IdentifierLookup = plpgsql_IdentifierLookup;
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
yylex();
argname = yylval.str;
plpgsql_IdentifierLookup = save_IdentifierLookup;
/* Match argument name to cursor arguments */
for (argpos = 0; argpos < row->nfields; argpos++)
{
if (strcmp(row->fieldnames[argpos], argname) == 0)
break;
}
if (argpos == row->nfields)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cursor \"%s\" has no argument named \"%s\"",
cursor->refname, argname),
parser_errposition(yylloc)));
/*
* Eat the ":=". We already peeked, so the error should never
* happen.
*/
tok2 = yylex();
if (tok2 != COLON_EQUALS)
yyerror("syntax error");
any_named = true;
}
else
argpos = argc;
if (argv[argpos] != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("value for parameter \"%s\" of cursor \"%s\" specified more than once",
row->fieldnames[argpos], cursor->refname),
parser_errposition(arglocation)));
/*
* Read the value expression. To provide the user with meaningful
* parse error positions, we check the syntax immediately, instead of
* checking the final expression that may have the arguments
* reordered. Trailing whitespace must not be trimmed, because
* otherwise input of the form (param -- comment\n, param) would be
* translated into a form where the second parameter is commented
* out.
*/
item = read_sql_construct(',', ')', 0,
",\" or \")",
sqlstart,
true, true,
false, /* do not trim */
NULL, &endtoken);
argv[argpos] = item->query + strlen(sqlstart);
if (endtoken == ')' && !(argc == row->nfields - 1))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("not enough arguments for cursor \"%s\"",
cursor->refname),
parser_errposition(yylloc)));
if (endtoken == ',' && (argc == row->nfields - 1))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("too many arguments for cursor \"%s\"",
cursor->refname),
parser_errposition(yylloc)));
}
/* Make positional argument list */
initStringInfo(&ds);
appendStringInfoString(&ds, sqlstart);
for (argc = 0; argc < row->nfields; argc++)
{
Assert(argv[argc] != NULL);
/*
* Because named notation allows permutated argument lists, include
* the parameter name for meaningful runtime errors.
*/
appendStringInfoString(&ds, argv[argc]);
if (any_named)
appendStringInfo(&ds, " AS %s",
quote_identifier(row->fieldnames[argc]));
if (argc < row->nfields - 1)
appendStringInfoString(&ds, ", ");
}
appendStringInfoChar(&ds, ';');
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
/* Next we'd better find the until token */
tok = yylex();
if (tok != until)
yyerror("syntax error");
return expr;
}
/*
* Parse RAISE ... USING options
*/
static List *
read_raise_options(void)
{
List *result = NIL;
for (;;)
{
PLpgSQL_raise_option *opt;
int tok;
if ((tok = yylex()) == 0)
yyerror("unexpected end of function definition");
opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option));
if (tok_is_keyword(tok, &yylval,
K_ERRCODE, "errcode"))
opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE;
else if (tok_is_keyword(tok, &yylval,
K_MESSAGE, "message"))
opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE;
else if (tok_is_keyword(tok, &yylval,
K_DETAIL, "detail"))
opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL;
else if (tok_is_keyword(tok, &yylval,
K_HINT, "hint"))
opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
else if (tok_is_keyword(tok, &yylval,
K_COLUMN, "column"))
opt->opt_type = PLPGSQL_RAISEOPTION_COLUMN;
else if (tok_is_keyword(tok, &yylval,
K_CONSTRAINT, "constraint"))
opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT;
else if (tok_is_keyword(tok, &yylval,
K_DATATYPE, "datatype"))
opt->opt_type = PLPGSQL_RAISEOPTION_DATATYPE;
else if (tok_is_keyword(tok, &yylval,
K_TABLE, "table"))
opt->opt_type = PLPGSQL_RAISEOPTION_TABLE;
else if (tok_is_keyword(tok, &yylval,
K_SCHEMA, "schema"))
opt->opt_type = PLPGSQL_RAISEOPTION_SCHEMA;
else
yyerror("unrecognized RAISE statement option");
tok = yylex();
if (tok != '=' && tok != COLON_EQUALS)
yyerror("syntax error, expected \"=\"");
opt->expr = read_sql_expression2(',', ';', ", or ;", &tok);
result = lappend(result, opt);
if (tok == ';')
break;
}
return result;
}
/*
* Check that the number of parameter placeholders in the message matches the
* number of parameters passed to it, if a message was given.
*/
static void
check_raise_parameters(PLpgSQL_stmt_raise *stmt)
{
char *cp;
int expected_nparams = 0;
if (stmt->message == NULL)
return;
for (cp = stmt->message; *cp; cp++)
{
if (cp[0] == '%')
{
/* ignore literal % characters */
if (cp[1] == '%')
cp++;
else
expected_nparams++;
}
}
if (expected_nparams < list_length(stmt->params))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("too many parameters specified for RAISE")));
if (expected_nparams > list_length(stmt->params))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("too few parameters specified for RAISE")));
}
/*
* Fix up CASE statement
*/
static PLpgSQL_stmt *
make_case(int location, PLpgSQL_expr *t_expr,
List *case_when_list, List *else_stmts)
{
PLpgSQL_stmt_case *new;
new = palloc(sizeof(PLpgSQL_stmt_case));
new->cmd_type = PLPGSQL_STMT_CASE;
new->lineno = plpgsql_location_to_lineno(location);
new->stmtid = ++plpgsql_curr_compile->nstatements;
new->t_expr = t_expr;
new->t_varno = 0;
new->case_when_list = case_when_list;
new->have_else = (else_stmts != NIL);
/* Get rid of list-with-NULL hack */
if (list_length(else_stmts) == 1 && linitial(else_stmts) == NULL)
new->else_stmts = NIL;
else
new->else_stmts = else_stmts;
/*
* When test expression is present, we create a var for it and then
* convert all the WHEN expressions to "VAR IN (original_expression)".
* This is a bit klugy, but okay since we haven't yet done more than
* read the expressions as text. (Note that previous parsing won't
* have complained if the WHEN ... THEN expression contained multiple
* comma-separated values.)
*/
if (t_expr)
{
char varname[32];
PLpgSQL_var *t_var;
ListCell *l;
/* use a name unlikely to collide with any user names */
snprintf(varname, sizeof(varname), "__Case__Variable_%d__",
plpgsql_nDatums);
/*
* We don't yet know the result datatype of t_expr. Build the
* variable as if it were INT4; we'll fix this at runtime if needed.
*/
t_var = (PLpgSQL_var *)
plpgsql_build_variable(varname, new->lineno,
plpgsql_build_datatype(INT4OID,
-1,
InvalidOid,
NULL),
true);
new->t_varno = t_var->dno;
foreach(l, case_when_list)
{
PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
PLpgSQL_expr *expr = cwt->expr;
StringInfoData ds;
/* copy expression query without SELECT keyword (expr->query + 7) */
Assert(strncmp(expr->query, "SELECT ", 7) == 0);
/* And do the string hacking */
initStringInfo(&ds);
appendStringInfo(&ds, "SELECT \"%s\" IN (%s)",
varname, expr->query + 7);
pfree(expr->query);
expr->query = pstrdup(ds.data);
/* Adjust expr's namespace to include the case variable */
expr->ns = plpgsql_ns_top();
pfree(ds.data);
}
}
return (PLpgSQL_stmt *) new;
}