4121 lines
107 KiB
Plaintext
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;
|
|
}
|