openGauss-server/src/common/pl/plpgsql/src/gram.y

12099 lines
495 KiB
Plaintext
Executable File

%{
/* -------------------------------------------------------------------------
*
* gram.y - Parser for the PL/pgSQL procedural language
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/pl/plpgsql/src/gram.y
*
* -------------------------------------------------------------------------
*/
#include "utils/plpgsql.h"
#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/gs_package.h"
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_synonym.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "commands/typecmds.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
#include "parser/analyze.h"
#include "parser/keywords.h"
#include "parser/parser.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "parser/scanner.h"
#include "parser/scansup.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pl_package.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
#include "knl/knl_session.h"
#include <limits.h>
#define LENGTH_OF_BRACKETS_AND_DOT 4
#define LENGTH_OF_QUOTATION_MARKS 2
#define IS_ANONYMOUS_BLOCK \
(u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile != NULL && \
strcmp(u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_signature, "inline_code_block") ==0)
#define IS_PACKAGE \
(u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package != NULL && \
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile == NULL)
/* 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)
/*
* Helper function to record statements label so that GOTO statements could reach
* there.
*/
static inline void
record_stmt_label(char * label, PLpgSQL_stmt *stmt)
{
/*
* Note, we do plpgsql parsing under "PL/pgSQL Function" memory context,
* so palloc memory for goto-lable element and the global array under it.
*/
PLpgSQL_gotoLabel *gl =
(PLpgSQL_gotoLabel *)palloc0(sizeof(PLpgSQL_gotoLabel));
gl->label = label;
gl->stmt = stmt;
u_sess->plsql_cxt.curr_compile_context->goto_labels = lappend(u_sess->plsql_cxt.curr_compile_context->goto_labels, (void *)gl);
}
/*
* 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 void yylex_inparam(StringInfoData* func_inparam,
int *params,
int * tok,
int *tableof_func_dno,
int *tableof_var_dno);
static int yylex_outparam(char** fieldnames,
int *varnos,
int nfields,
PLpgSQL_row **row,
PLpgSQL_rec **rec,
int *token,
bool is_push_back,
bool overload = false);
static bool is_function(const char *name, bool is_assign, bool no_parenthesis, List* funcNameList = NULL);
static bool is_unreservedkeywordfunction(ScanKeyword* keyword, bool no_parenthesis, const char *name);
static bool is_paren_friendly_datatype(TypeName *name);
static void plpgsql_parser_funcname(const char *s, char **output,
int numidents);
static PLpgSQL_stmt *make_callfunc_stmt(const char *sqlstart,
int location, bool is_assign, bool eaten_first_token, List* funcNameList = NULL, int arrayFuncDno = -1, bool isCallFunc = false);
static PLpgSQL_stmt *make_callfunc_stmt_no_arg(const char *sqlstart, int location, bool withsemicolon = false, List* funcNameList = NULL);
static PLpgSQL_expr *read_sql_construct6(int until,
int until2,
int until3,
int until4,
int until5,
int until6,
const char *expected,
const char *sqlstart,
bool isexpression,
bool valid_sql,
bool trim,
int *startloc,
int *endtoken,
DList* tokenstack = NULL,
bool issaveexpr = false);
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 *parse_lob_open_close(int location);
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 char *CopyNameOfDatum(PLwdatum *wdatum);
static void check_assignable(PLpgSQL_datum *datum, int location);
static void read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row,
bool *strict, bool bulk_collect);
static PLpgSQL_row *read_into_scalar_list(char *initial_name,
PLpgSQL_datum *initial_datum,
int initial_dno,
int initial_location);
static PLpgSQL_row *read_into_array_table_scalar_list(char *initial_name,
PLpgSQL_datum *initial_datum,
int initial_dno,
int initial_location,
bool bulk_collect);
static void read_using_target(List **in_expr, PLpgSQL_row **out_row);
static PLpgSQL_row *read_into_placeholder_scalar_list(char *initial_name,
int initial_location);
static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int initial_dno,
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 int errstate = ERROR;
static char* get_proc_str(int tok);
static char* get_init_proc(int tok);
static char* get_attrname(int tok);
static AttrNumber get_assign_attrno(PLpgSQL_datum* target, char* attrname);
static void raw_parse_package_function(char* proc_str);
static void checkFuncName(List* funcname);
static void IsInPublicNamespace(char* varname);
static void SetErrorState();
static void AddNamespaceIfNeed(int dno, char* ident);
static void AddNamespaceIfPkgVar(const char* ident, IdentifierLookup save_IdentifierLookup);
bool plpgsql_is_token_keyword(int tok);
static void check_bulk_into_type(PLpgSQL_row* row);
static void check_table_index(PLpgSQL_datum* datum, char* funcName);
static PLpgSQL_type* build_type_from_record_var(int dno);
static PLpgSQL_type * build_array_type_from_elemtype(PLpgSQL_type *elem_type);
static PLpgSQL_var* plpgsql_build_nested_variable(PLpgSQL_var *nest_table, bool isconst, char* name, int lineno);
static void read_multiset(StringInfoData* ds, char* tableName1, Oid typeOid1);
static Oid get_table_type(PLpgSQL_datum* datum);
static void read_multiset(StringInfoData* ds, char* tableName1, Oid typeOid1);
static Oid get_table_type(PLpgSQL_datum* datum);
static Node* make_columnDef_from_attr(PLpgSQL_rec_attr* attr);
static TypeName* make_typename_from_datatype(PLpgSQL_type* datatype);
static Oid plpgsql_build_package_record_type(const char* typname, List* list, bool add2namespace);
static void plpgsql_build_package_array_type(const char* typname, Oid elemtypoid, char arraytype);
static void plpgsql_build_package_refcursor_type(const char* typname);
int plpgsql_yylex_single(void);
static void get_datum_tok_type(PLpgSQL_datum* target, int* tok_flag);
static bool copy_table_var_indents(char* tableName, char* idents, int tablevar_namelen);
static int push_back_token_stack(DList* tokenstack);
static int read_assignlist(bool is_push_back, int* token);
static void plpgsql_cast_reference_list(List* idents, StringInfoData* ds, bool isPkgVar);
static bool PkgVarNeedCast(List* idents);
static void CastArrayNameToArrayFunc(StringInfoData* ds, List* idents, bool needDot = true);
static Oid get_table_index_type(PLpgSQL_datum* datum, int *tableof_func_dno);
static int get_nest_tableof_layer(PLpgSQL_var *var, const char *typname, int errstate);
static void SetErrorState();
static void CheckDuplicateFunctionName(List* funcNameList);
static void check_autonomous_nest_tablevar(PLpgSQL_var* var);
#ifndef ENABLE_MULTIPLE_NODES
static PLpgSQL_type* build_type_from_cursor_var(PLpgSQL_var* var);
static bool checkAllAttrName(TupleDesc tupleDesc);
static void BuildForQueryVariable(PLpgSQL_expr* expr, PLpgSQL_row **row, PLpgSQL_rec **rec,
const char* refname, int lineno);
#endif
static Oid createCompositeTypeForCursor(PLpgSQL_var* var, PLpgSQL_expr* expr);
static void check_record_nest_tableof_index(PLpgSQL_datum* datum);
static void check_tableofindex_args(int tableof_var_dno, Oid argtype);
static bool need_build_row_for_func_arg(PLpgSQL_rec **rec, PLpgSQL_row **row, int out_arg_num, int all_arg, int *varnos, char *p_argmodes);
%}
%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_rec *rec;
PLpgSQL_row *row;
int dno;
} forvariable;
struct
{
char *label;
int n_initvars;
int *initvarnos;
bool isAutonomous;
} 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;
PLpgSQL_rec_attr *recattr;
Node *plnode;
}
%type <plnode> assign_el
%type <declhdr> decl_sect
%type <varname> decl_varname
%type <boolean> decl_const decl_notnull exit_type
%type <expr> decl_defval decl_rec_defval decl_cursor_query
%type <dtype> decl_datatype
%type <oid> decl_collate
%type <datum> decl_cursor_args
%type <list> decl_cursor_arglist assign_list
%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 <ival> assign_var foreach_slice error_code cursor_variable
%type <datum> decl_cursor_arg
%type <forvariable> for_variable
%type <stmt> for_control forall_control
%type <str> any_identifier opt_block_label opt_label goto_block_label label_name spec_proc init_proc
%type <str> opt_rollback_to opt_savepoint_name savepoint_name attr_name
%type <list> proc_sect proc_stmts stmt_elsifs stmt_else forall_body
%type <loop_body> loop_body
%type <stmt> proc_stmt pl_block
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit stmt_goto label_stmts label_stmt
%type <stmt> stmt_return stmt_raise stmt_execsql
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
%type <stmt> stmt_commit stmt_rollback stmt_savepoint
%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 <ival> getdiag_item getdiag_target
%type <ival> varray_var
%type <ival> table_var
%type <ival> record_var
%type <expr> expr_until_parenthesis
%type <ival> opt_scrollable
%type <fetch> opt_fetch_direction
%type <expr> fetch_limit_expr
%type <datum> fetch_into_target
%type <keyword> unreserved_keyword
%type <list> record_attr_list
%type <recattr> record_attr
%type <boolean> opt_save_exceptions
%type <keyword> unreserved_keyword_func
/*
* 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 FCONST SCONST BCONST VCONST XCONST Op CmpOp COMMENTSTRING
%token <ival> ICONST PARAM
%token TYPECAST ORA_JOINOP DOT_DOT COLON_EQUALS PARA_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 <word> T_PLACEHOLDER /* place holder , for IN/OUT parameters */
%token <wdatum> T_VARRAY T_ARRAY_FIRST T_ARRAY_LAST T_ARRAY_COUNT T_ARRAY_EXISTS T_ARRAY_PRIOR T_ARRAY_NEXT T_ARRAY_DELETE T_ARRAY_EXTEND T_ARRAY_TRIM T_VARRAY_VAR T_RECORD
%token <wdatum> T_TABLE T_TABLE_VAR T_PACKAGE_VARIABLE
%token <wdatum> T_PACKAGE_CURSOR_ISOPEN T_PACKAGE_CURSOR_FOUND T_PACKAGE_CURSOR_NOTFOUND T_PACKAGE_CURSOR_ROWCOUNT
%token LESS_LESS
%token GREATER_GREATER
%token <wdatum> T_REFCURSOR /* token for cursor type */
%token T_SQL_ISOPEN
%token T_SQL_FOUND
%token T_SQL_NOTFOUND
%token T_SQL_ROWCOUNT
%token T_SQL_BULK_EXCEPTIONS
%token T_CURSOR_ISOPEN
%token T_CURSOR_FOUND
%token T_CURSOR_NOTFOUND
%token T_CURSOR_ROWCOUNT
/*
* 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_ALTER
%token <keyword> K_ARRAY
%token <keyword> K_AS
%token <keyword> K_BACKWARD
%token <keyword> K_BEGIN
%token <keyword> K_BULK
%token <keyword> K_BY
%token <keyword> K_CASE
%token <keyword> K_CLOSE
%token <keyword> K_COLLATE
%token <keyword> K_COLLECT
%token <keyword> K_COMMIT
%token <keyword> K_CONSTANT
%token <keyword> K_CONTINUE
%token <keyword> K_CURRENT
%token <keyword> K_CURSOR
%token <keyword> K_DEBUG
%token <keyword> K_DECLARE
%token <keyword> K_DEFAULT
%token <keyword> K_DELETE
%token <keyword> K_DETAIL
%token <keyword> K_DETERMINISTIC
%token <keyword> K_DIAGNOSTICS
%token <keyword> K_DISTINCT
%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_EXCEPT
%token <keyword> K_EXCEPTION
%token <keyword> K_EXCEPTIONS
%token <keyword> K_EXECUTE
%token <keyword> K_EXIT
%token <keyword> K_FETCH
%token <keyword> K_FIRST
%token <keyword> K_FOR
%token <keyword> K_FORALL
%token <keyword> K_FOREACH
%token <keyword> K_FORWARD
%token <keyword> K_FROM
%token <keyword> K_FUNCTION
%token <keyword> K_GET
%token <keyword> K_GOTO
%token <keyword> K_HINT
%token <keyword> K_IF
%token <keyword> K_IMMEDIATE
%token <keyword> K_INSTANTIATION
%token <keyword> K_IN
%token <keyword> K_INDEX
%token <keyword> K_INFO
%token <keyword> K_INSERT
%token <keyword> K_INTERSECT
%token <keyword> K_INTO
%token <keyword> K_IS
%token <keyword> K_LAST
%token <keyword> K_LIMIT
%token <keyword> K_LOG
%token <keyword> K_LOOP
%token <keyword> K_MERGE
%token <keyword> K_MESSAGE
%token <keyword> K_MESSAGE_TEXT
%token <keyword> K_MOVE
%token <keyword> K_MULTISET
%token <keyword> K_MULTISETS
%token <keyword> K_NEXT
%token <keyword> K_NO
%token <keyword> K_NOT
%token <keyword> K_NOTICE
%token <keyword> K_NULL
%token <keyword> K_OF
%token <keyword> K_OPEN
%token <keyword> K_OPTION
%token <keyword> K_OR
%token <keyword> K_OUT
%token <keyword> K_PACKAGE
%token <keyword> K_PERFORM
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
%token <keyword> K_PRAGMA
%token <keyword> K_PRIOR
%token <keyword> K_PROCEDURE
%token <keyword> K_QUERY
%token <keyword> K_RAISE
%token <keyword> K_RECORD
%token <keyword> K_REF
%token <keyword> K_RELATIVE
%token <keyword> K_RELEASE
%token <keyword> K_RESULT_OID
%token <keyword> K_RETURN
%token <keyword> K_RETURNED_SQLSTATE
%token <keyword> K_REVERSE
%token <keyword> K_ROLLBACK
%token <keyword> K_ROWTYPE
%token <keyword> K_ROW_COUNT
%token <keyword> K_SAVE
%token <keyword> K_SAVEPOINT
%token <keyword> K_SELECT
%token <keyword> K_SCROLL
%token <keyword> K_SLICE
%token <keyword> K_SQLSTATE
%token <keyword> K_STACKED
%token <keyword> K_STRICT
%token <keyword> K_SYS_REFCURSOR
%token <keyword> K_TABLE
%token <keyword> K_THEN
%token <keyword> K_TO
%token <keyword> K_TYPE
%token <keyword> K_UNION
%token <keyword> K_UPDATE
%token <keyword> K_USE_COLUMN
%token <keyword> K_USE_VARIABLE
%token <keyword> K_USING
%token <keyword> K_VARIABLE_CONFLICT
%token <keyword> K_VARRAY
%token <keyword> K_WARNING
%token <keyword> K_WHEN
%token <keyword> K_WHILE
%token <keyword> K_WITH
%%
pl_body : pl_package_spec
| pl_function
| pl_package_init
;
pl_package_spec : K_PACKAGE { SetErrorState(); } decl_sect K_END
{
int nDatums = 0;
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile!=NULL) {
nDatums = u_sess->plsql_cxt.curr_compile_context->plpgsql_nDatums;
} else {
nDatums = u_sess->plsql_cxt.curr_compile_context->plpgsql_pkg_nDatums;
}
for (int i = 0; i < nDatums; i++) {
u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[i]->ispkg = true;
}
PLpgSQL_nsitem* ns_cur = plpgsql_ns_top();
while (ns_cur != NULL) {
if (ns_cur != NULL && ns_cur->pkgname == NULL) {
ns_cur->pkgname = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_signature;
}
ns_cur = ns_cur->prev;
}
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->n_initvars = $3.n_initvars;
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->initvarnos = $3.initvarnos;
}
;
pl_package_init : K_INSTANTIATION { SetErrorState(); } init_proc
{
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package != NULL)
{
List* raw_parsetree_list = NULL;
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
raw_parsetree_list = raw_parser($3);
DoStmt* stmt;
stmt = (DoStmt *)linitial(raw_parsetree_list);
stmt->isExecuted = false;
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->is_spec_compiling) {
stmt->isSpec = true;
} else {
stmt->isSpec = false;
}
List *proc_list = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->proc_list;
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->proc_list = lappend(proc_list,stmt);
} else {
yyerror("instantiation only use in package compile");
}
}
pl_function : comp_options { SetErrorState(); } pl_block opt_semi
{
u_sess->plsql_cxt.curr_compile_context->plpgsql_parse_result = (PLpgSQL_stmt_block *) $3;
}
;
comp_options :
| comp_options comp_option
;
comp_option : '#' K_OPTION K_DUMP
{
u_sess->plsql_cxt.curr_compile_context->plpgsql_DumpExecTree = true;
}
| '#' K_VARIABLE_CONFLICT K_ERROR
{
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_ERROR;
}
| '#' K_VARIABLE_CONFLICT K_USE_VARIABLE
{
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_VARIABLE;
}
| '#' K_VARIABLE_CONFLICT K_USE_COLUMN
{
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_COLUMN;
}
;
opt_semi :
| ';'
;
pl_block : decl_sect K_BEGIN proc_sect exception_sect K_END opt_label
{
PLpgSQL_stmt_block *newp;
newp = (PLpgSQL_stmt_block *)palloc0(sizeof(PLpgSQL_stmt_block));
newp->cmd_type = PLPGSQL_STMT_BLOCK;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->sqlString = plpgsql_get_curline_query();
newp->label = $1.label;
newp->isAutonomous = $1.isAutonomous;
newp->n_initvars = $1.n_initvars;
newp->initvarnos = $1.initvarnos;
newp->body = $3;
newp->exceptions = $4;
check_labels($1.label, $6, @6);
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)newp;
/* register the stmt if it is labeled */
record_stmt_label($1.label, (PLpgSQL_stmt *)newp);
}
;
decl_sect : opt_block_label
{
/* done with decls, so resume identifier lookup */
u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
$$.label = $1;
$$.n_initvars = 0;
$$.initvarnos = NULL;
$$.isAutonomous = false;
}
| opt_block_label decl_start
{
u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
$$.label = $1;
$$.n_initvars = 0;
$$.initvarnos = NULL;
$$.isAutonomous = false;
}
| opt_block_label decl_start { SetErrorState(); } decl_stmts
{
u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
$$.label = $1;
/* Remember variables declared in decl_stmts */
$$.n_initvars = plpgsql_add_initdatums(&($$.initvarnos));
$$.isAutonomous = u_sess->plsql_cxt.pragma_autonomous;
u_sess->plsql_cxt.pragma_autonomous = false;
}
;
decl_start : K_DECLARE
{
/* Forget any variables created before block */
plpgsql_add_initdatums(NULL);
u_sess->plsql_cxt.pragma_autonomous = false;
/*
* Disable scanner lookup of identifiers while
* we process the decl_stmts
*/
u_sess->plsql_cxt.curr_compile_context->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.
*/
const char* message = "block label must be placed before DECLARE, not after";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("block label must be placed before DECLARE, not after"),
parser_errposition(@1)));
}
;
as_is : K_IS
| K_AS /* A db */
;
decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull decl_defval
{
PLpgSQL_variable *var;
IsInPublicNamespace($1.name);
/*
* 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 ($3 == NULL) {
// not allowed going on when plsql_show_all_error is on
#ifndef ENABLE_MULTIPLE_NODES
if (!u_sess->attr.attr_common.plsql_show_all_error) {
yyerror("missing data type declaration");
} else {
const char* message = "missing data type declaration";
InsertErrorMessage(message, @4);
yyerror("missing data type declaration");
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("missing data type declaration"),
parser_errposition(@4)));
}
#else
yyerror("missing data type declaration");
#endif
} else {
if ($3 && $3->typoid == REFCURSOROID && IsOnlyCompilePackage())
{
yyerror("not allow use ref cursor in package");
}
if (OidIsValid($4))
{
if (!OidIsValid($3->collation)) {
const char* message = "collations are not supported by type";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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);
if ($2)
{
if (var->dtype == PLPGSQL_DTYPE_VAR)
((PLpgSQL_var *) var)->isconst = $2;
else {
const char* message = "row or record variable cannot be CONSTANT";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("row or record variable cannot be CONSTANT"),
parser_errposition(@2)));
}
}
if ($5)
{
if (var->dtype == PLPGSQL_DTYPE_VAR)
((PLpgSQL_var *) var)->notnull = $5;
else {
const char* message = "row or record variable cannot be NOT NULL";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("row or record variable cannot be NOT NULL"),
parser_errposition(@4)));
}
}
if ($6 != NULL)
{
if (var->dtype == PLPGSQL_DTYPE_VAR)
((PLpgSQL_var *) var)->default_val = $6;
else {
((PLpgSQL_row *) var)->default_val = $6;
}
}
}
pfree_ext($1.name);
}
| decl_varname K_ALIAS K_FOR decl_aliasitem ';'
{
IsInPublicNamespace($1.name);
plpgsql_ns_additem($4->itemtype,
$4->itemno, $1.name);
pfree_ext($1.name);
}
| K_CURSOR decl_varname opt_scrollable
{
IsInPublicNamespace($2.name);
plpgsql_ns_push($2.name);
}
decl_cursor_args decl_is_for decl_cursor_query
{
PLpgSQL_var *newp;
/* pop local namespace for cursor args */
plpgsql_ns_pop();
newp = (PLpgSQL_var *)
plpgsql_build_variable($2.name, $2.lineno,
plpgsql_build_datatype(REFCURSOROID,
-1,
InvalidOid),
true);
newp->cursor_explicit_expr = $7;
if ($5 == NULL)
newp->cursor_explicit_argrow = -1;
else
newp->cursor_explicit_argrow = $5->dno;
newp->cursor_options = CURSOR_OPT_FAST_PLAN | $3;
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
newp->datatype->cursorCompositeOid = IS_ANONYMOUS_BLOCK ?
InvalidOid : createCompositeTypeForCursor(newp, $7);
pfree_ext($2.name);
}
| K_TYPE decl_varname as_is K_REF K_CURSOR ';'
{
IsInPublicNamespace($2.name);
/* add name of cursor type to PLPGSQL_NSTYPE_REFCURSOR */
plpgsql_ns_additem(PLPGSQL_NSTYPE_REFCURSOR,0,$2.name);
if (IS_PACKAGE) {
plpgsql_build_package_refcursor_type($2.name);
}
pfree_ext($2.name);
}
| decl_varname T_REFCURSOR ';'
{
IsInPublicNamespace($1.name);
if (IsOnlyCompilePackage()) {
yyerror("not allow use ref cursor in package");
}
AddNamespaceIfNeed(-1, $2.ident);
plpgsql_build_variable(
$1.name,
$1.lineno,
plpgsql_build_datatype(REFCURSOROID,-1,InvalidOid),
true);
pfree_ext($1.name);
}
| decl_varname K_SYS_REFCURSOR ';'
{
IsInPublicNamespace($1.name);
if (IsOnlyCompilePackage()) {
yyerror("not allow use ref cursor in package");
}
plpgsql_build_variable(
$1.name,
$1.lineno,
plpgsql_build_datatype(REFCURSOROID,-1,InvalidOid),
true);
pfree_ext($1.name);
}
/*
* Implementing the grammar pattern
* "type varrayname is varray(ICONST) of datatype;"
* and "varname varrayname := varrayname()"
*/
| K_TYPE decl_varname as_is K_VARRAY '(' ICONST ')' K_OF decl_datatype ';'
{
IsInPublicNamespace($2.name);
$9->collectionType = PLPGSQL_COLLECTION_ARRAY;
$9->tableOfIndexType = InvalidOid;
if($9->typinput.fn_oid == F_ARRAY_IN) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or table type nested by array type is not supported yet."),
errdetail("Define array type \"%s\" of array or table type is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of array type")));
u_sess->plsql_cxt.have_error = true;
}
if($9->typoid == REFCURSOROID) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("ref cursor type nested by array is not supported yet."),
errdetail("Define array type \"%s\" of ref cursor type is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of array type")));
u_sess->plsql_cxt.have_error = true;
}
plpgsql_build_varrayType($2.name, $2.lineno, $9, true);
if (IS_PACKAGE) {
plpgsql_build_package_array_type($2.name, $9->typoid, TYPCATEGORY_ARRAY);
}
pfree_ext($2.name);
}
| K_TYPE decl_varname as_is K_VARRAY '(' ICONST ')' K_OF record_var ';'
{
#ifdef ENABLE_MULTIPLE_NODES
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or record type nesting is not supported in distributed database yet."),
errdetail("Define array type \"%s\" of record is not supported in distributed database yet.", $2.name),
errcause("feature not supported"),
erraction("check define of array type")));
u_sess->plsql_cxt.have_error = true;
#endif
if (IS_ANONYMOUS_BLOCK) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or record type nesting is not supported in anonymous block yet."),
errdetail("Define array type \"%s\" of record is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of array type")));
u_sess->plsql_cxt.have_error = true;
}
IsInPublicNamespace($2.name);
PLpgSQL_type *newp = NULL;
newp = build_type_from_record_var($9);
newp->collectionType = PLPGSQL_COLLECTION_ARRAY;
newp->tableOfIndexType = InvalidOid;
plpgsql_build_varrayType($2.name, $2.lineno, newp, true);
if (IS_PACKAGE) {
plpgsql_build_package_array_type($2.name, newp->typoid, TYPCATEGORY_ARRAY);
}
pfree_ext($2.name);
}
| K_TYPE decl_varname as_is K_VARRAY '(' ICONST ')' K_OF varray_var ';'
{
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array type nested by array is not supported yet."),
errdetail("Define array type \"%s\" of array is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of array type")));
u_sess->plsql_cxt.have_error = true;
}
| K_TYPE decl_varname as_is K_VARRAY '(' ICONST ')' K_OF table_var ';'
{
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("table type nested by array is not supported yet."),
errdetail("Define array type \"%s\" of table type is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of array type")));
u_sess->plsql_cxt.have_error = true;
}
| K_TYPE decl_varname as_is K_VARRAY '(' ICONST ')' K_OF T_REFCURSOR ';'
{
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("ref cursor type nested by array is not supported yet."),
errdetail("Define array type \"%s\" of ref cursor type is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of array type")));
u_sess->plsql_cxt.have_error = true;
}
| decl_varname decl_const varray_var decl_defval
{
IsInPublicNamespace($1.name);
PLpgSQL_type *var_type = ((PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$3])->datatype;
PLpgSQL_var *newp;
PLpgSQL_type *new_var_type;
new_var_type = build_array_type_from_elemtype(var_type);
new_var_type->collectionType = var_type->collectionType;
new_var_type->tableOfIndexType = var_type->tableOfIndexType;
newp = (PLpgSQL_var *)plpgsql_build_variable($1.name, $1.lineno, new_var_type, true);
newp->isconst = $2;
newp->default_val = $4;
if (NULL == newp) {
const char* message = "build variable failed";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("build variable failed")));
u_sess->plsql_cxt.have_error = true;
}
pfree_ext($1.name);
}
| K_TYPE decl_varname as_is K_TABLE K_OF decl_datatype decl_notnull ';'
{
#ifdef ENABLE_MULTIPLE_NODES
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("table of is not support in distribute database"),
errdetail("N/A"), errcause("PL/SQL uses unsupported feature."),
erraction("Modify SQL statement according to the manual.")));
#endif
if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("table of is only support in A-format database."),
errdetail("N/A"), errcause("PL/SQL uses unsupported feature."),
erraction("Modify SQL statement according to the manual.")));
}
IsInPublicNamespace($2.name);
$6->collectionType = PLPGSQL_COLLECTION_TABLE;
$6->tableOfIndexType = InvalidOid;
if($6->typinput.fn_oid == F_ARRAY_IN) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or table type nested by table type is not supported yet."),
errdetail("Define table type \"%s\" of array or table type is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
if($6->typoid == REFCURSOROID) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("ref cursor type nested by table type is not supported yet."),
errdetail("Define table type \"%s\" of ref cursor type is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
plpgsql_build_tableType($2.name, $2.lineno, $6, true);
if (IS_PACKAGE) {
plpgsql_build_package_array_type($2.name, $6->typoid, TYPCATEGORY_TABLEOF);
}
pfree_ext($2.name);
}
| K_TYPE decl_varname as_is K_TABLE K_OF table_var decl_notnull ';'
{
IsInPublicNamespace($2.name);
PLpgSQL_var *check_var = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$6];
/* get and check nest tableof's depth */
int depth = get_nest_tableof_layer(check_var, $2.name, errstate);
PLpgSQL_type *nest_type = plpgsql_build_nested_datatype();
nest_type->tableOfIndexType = INT4OID;
nest_type->collectionType = PLPGSQL_COLLECTION_TABLE;
PLpgSQL_var* var = (PLpgSQL_var*)plpgsql_build_tableType($2.name, $2.lineno, nest_type, true);
/* nested table type */
var->nest_table = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$6];
var->nest_layers = depth;
var->isIndexByTblOf = false;
pfree_ext($2.name);
}
| K_TYPE decl_varname as_is K_TABLE K_OF record_var decl_notnull ';'
{
#ifdef ENABLE_MULTIPLE_NODES
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or record type nesting is not supported in distributed database yet."),
errdetail("Define table type \"%s\" of record is not supported in distributed database yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
#endif
if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("table of is only support in A-format database."),
errdetail("N/A"), errcause("PL/SQL uses unsupported feature."),
erraction("Modify SQL statement according to the manual.")));
}
if (IS_ANONYMOUS_BLOCK) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or record type nesting is not supported in anonymous block yet."),
errdetail("Define table type \"%s\" of record is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
IsInPublicNamespace($2.name);
PLpgSQL_type *newp = NULL;
newp = build_type_from_record_var($6);
newp->collectionType = PLPGSQL_COLLECTION_TABLE;
newp->tableOfIndexType = InvalidOid;
plpgsql_build_tableType($2.name, $2.lineno, newp, true);
if (IS_PACKAGE) {
plpgsql_build_package_array_type($2.name, newp->typoid, TYPCATEGORY_TABLEOF);
}
pfree_ext($2.name);
}
| K_TYPE decl_varname as_is K_TABLE K_OF varray_var decl_notnull ';'
{
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array type nested by table type is not supported yet."),
errdetail("Define table type \"%s\" of array is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
| K_TYPE decl_varname as_is K_TABLE K_OF T_REFCURSOR decl_notnull ';'
{
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("ref cursor type nested by table type is not supported yet."),
errdetail("Define table type \"%s\" of ref cursor is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
| K_TYPE decl_varname as_is K_TABLE K_OF varray_var decl_notnull K_INDEX K_BY decl_datatype ';'
{
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array type nested by table type is not supported yet."),
errdetail("Define table type \"%s\" of array is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
| K_TYPE decl_varname as_is K_TABLE K_OF T_REFCURSOR decl_notnull K_INDEX K_BY decl_datatype ';'
{
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("ref cursor type nested by table type is not supported yet."),
errdetail("Define table type \"%s\" of ref cursor type is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
| K_TYPE decl_varname as_is K_TABLE K_OF decl_datatype decl_notnull K_INDEX K_BY decl_datatype ';'
{
#ifdef ENABLE_MULTIPLE_NODES
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("table of is not support in distribute database"),
errdetail("N/A"), errcause("PL/SQL uses unsupported feature."),
erraction("Modify SQL statement according to the manual.")));
#endif
if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("table of is only support in A-format database."),
errdetail("N/A"), errcause("PL/SQL uses unsupported feature."),
erraction("Modify SQL statement according to the manual.")));
}
IsInPublicNamespace($2.name);
$6->collectionType = PLPGSQL_COLLECTION_TABLE;
if ($10->typoid != VARCHAROID && $10->typoid != INT4OID) {
const char* message = "unsupported table index type";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("unsupported table index type")));
u_sess->plsql_cxt.have_error = true;
}
$6->tableOfIndexType = $10->typoid;
if($6->typinput.fn_oid == F_ARRAY_IN) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or table type nested by table type is not supported yet."),
errdetail("Define table type \"%s\" of array or table type is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
if($6->typoid == REFCURSOROID) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("ref cursor type nested by table type is not supported yet."),
errdetail("Define table type \"%s\" of ref cursor type is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
PLpgSQL_var* var = (PLpgSQL_var*)plpgsql_build_tableType($2.name, $2.lineno, $6, true);
var->isIndexByTblOf = true;
if (IS_PACKAGE) {
if ($10->typoid == VARCHAROID) {
plpgsql_build_package_array_type($2.name, $6->typoid, TYPCATEGORY_TABLEOF_VARCHAR);
} else {
plpgsql_build_package_array_type($2.name, $6->typoid, TYPCATEGORY_TABLEOF_INTEGER);
}
}
pfree_ext($2.name);
}
| K_TYPE decl_varname as_is K_TABLE K_OF table_var decl_notnull K_INDEX K_BY decl_datatype ';'
{
IsInPublicNamespace($2.name);
if ($10->typoid != VARCHAROID && $10->typoid != INT4OID) {
const char* message = "unsupported table index type";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("unsupported table index type")));
u_sess->plsql_cxt.have_error = true;
}
PLpgSQL_var *check_var = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$6];
/* get and check nest tableof's depth */
int depth = get_nest_tableof_layer(check_var, $2.name, errstate);
PLpgSQL_type *nest_type = plpgsql_build_nested_datatype();
nest_type->tableOfIndexType = $10->typoid;
nest_type->collectionType = PLPGSQL_COLLECTION_TABLE;
PLpgSQL_var* var = (PLpgSQL_var*)plpgsql_build_tableType($2.name, $2.lineno, nest_type, true);
/* nested table type */
var->nest_table = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$6];
var->nest_layers = depth;
var->isIndexByTblOf = true;
pfree_ext($2.name);
}
| K_TYPE decl_varname as_is K_TABLE K_OF record_var decl_notnull K_INDEX K_BY decl_datatype ';'
{
#ifdef ENABLE_MULTIPLE_NODES
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or record type nesting is not supported in distributed database yet."),
errdetail("Define table type \"%s\" of record is not supported in distributed database yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
#endif
if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("table of is only support in A-format database."),
errdetail("N/A"), errcause("PL/SQL uses unsupported feature."),
erraction("Modify SQL statement according to the manual.")));
}
if (IS_ANONYMOUS_BLOCK) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or record type nesting is not supported in anonymous block yet."),
errdetail("Define table type \"%s\" of record is not supported yet.", $2.name),
errcause("feature not supported"),
erraction("check define of table type")));
u_sess->plsql_cxt.have_error = true;
}
IsInPublicNamespace($2.name);
PLpgSQL_type *newp = NULL;
newp = build_type_from_record_var($6);
newp->collectionType = PLPGSQL_COLLECTION_TABLE;
if ($10->typoid != VARCHAROID && $10->typoid != INT4OID) {
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("unsupported table index type")));
u_sess->plsql_cxt.have_error = true;
}
newp->tableOfIndexType = $10->typoid;
PLpgSQL_var *var = (PLpgSQL_var*)plpgsql_build_tableType($2.name, $2.lineno, newp, true);
var->isIndexByTblOf = true;
if (IS_PACKAGE) {
if ($10->typoid == VARCHAROID) {
plpgsql_build_package_array_type($2.name, newp->typoid, TYPCATEGORY_TABLEOF_VARCHAR);
} else {
plpgsql_build_package_array_type($2.name, newp->typoid, TYPCATEGORY_TABLEOF_INTEGER);
}
}
pfree_ext($2.name);
}
| decl_varname decl_const table_var decl_defval
{
IsInPublicNamespace($1.name);
PLpgSQL_type *var_type = ((PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$3])->datatype;
PLpgSQL_var *table_type = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$3];
PLpgSQL_var *newp;
PLpgSQL_type *new_var_type;
new_var_type = build_array_type_from_elemtype(var_type);
new_var_type->collectionType = var_type->collectionType;
new_var_type->tableOfIndexType = var_type->tableOfIndexType;
newp = (PLpgSQL_var *)plpgsql_build_variable($1.name, $1.lineno, new_var_type, true);
if (NULL == newp) {
const char* message = "build variable failed";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("build variable failed")));
u_sess->plsql_cxt.have_error = true;
}
newp->isconst = $2;
newp->default_val = $4;
if (table_type->nest_table != NULL) {
newp->nest_table = plpgsql_build_nested_variable(table_type->nest_table, $2, $1.name, $1.lineno);
newp->nest_layers = table_type->nest_layers;
}
pfree_ext($1.name);
}
| K_TYPE decl_varname as_is K_RECORD '(' record_attr_list ')' ';'
{
IsInPublicNamespace($2.name);
PLpgSQL_rec_type *newp = NULL;
newp = plpgsql_build_rec_type($2.name, $2.lineno, $6, true);
if (NULL == newp) {
const char* message = "build variable failed";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("build variable failed")));
u_sess->plsql_cxt.have_error = true;
}
if (IS_PACKAGE) {
newp->typoid = plpgsql_build_package_record_type($2.name, $6, true);
}
pfree_ext($2.name);
}
| decl_varname record_var decl_defval
{
IsInPublicNamespace($1.name);
PLpgSQL_var *newp = NULL;
PLpgSQL_type * var_type = (PLpgSQL_type *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$2];
newp = (PLpgSQL_var *)plpgsql_build_variable($1.name,$1.lineno,
var_type,true);
if (NULL == newp) {
const char* message = "build variable failed";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("build variable failed")));
u_sess->plsql_cxt.have_error = true;
}
if ($3 != NULL) {
((PLpgSQL_row *) newp)->default_val = $3;
}
pfree_ext($1.name);
}
| K_FUNCTION {u_sess->parser_cxt.is_procedure=false;} spec_proc
{
u_sess->plsql_cxt.procedure_start_line = GetLineNumber(u_sess->plsql_cxt.curr_compile_context->core_yy->scanbuf, @1);
u_sess->plsql_cxt.plpgsql_yylloc = @1;
u_sess->plsql_cxt.isCreateFunction = true;
raw_parse_package_function($3);
u_sess->plsql_cxt.isCreateFunction = false;
u_sess->plsql_cxt.procedure_start_line = 0;
u_sess->plsql_cxt.plpgsql_yylloc = 0;
}
| K_PROCEDURE {u_sess->parser_cxt.is_procedure=true;} spec_proc
{
u_sess->plsql_cxt.procedure_start_line = GetLineNumber(u_sess->plsql_cxt.curr_compile_context->core_yy->scanbuf, @1);
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
u_sess->plsql_cxt.isCreateFunction = true;
raw_parse_package_function($3);
u_sess->plsql_cxt.isCreateFunction = false;
u_sess->plsql_cxt.procedure_start_line = 0;
u_sess->plsql_cxt.plpgsql_yylloc = 0;
}
| K_PRAGMA any_identifier ';'
{
if (pg_strcasecmp($2, "autonomous_transaction") == 0) {
u_sess->plsql_cxt.pragma_autonomous = true;
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile !=NULL) {
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->is_autonomous = true;
}
}
else {
elog(errstate, "invalid pragma");
u_sess->plsql_cxt.have_error = true;
}
}
| K_PRAGMA any_identifier '(' any_identifier ',' error_code ')' ';'
{
if (pg_strcasecmp($2, "exception_init") == 0) {
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT) {
plpgsql_set_variable($4, $6);
}
} else {
elog(errstate, "invalid pragma.");
u_sess->plsql_cxt.have_error = true;
}
}
;
error_code : ICONST
{
$$ = $1;
}
| '-' ICONST
{
$$ = -$2;
}
spec_proc :
{
$$ = get_proc_str(yychar);
yyclearin;
}
init_proc :
{
$$ = get_init_proc(yychar);
yyclearin;
}
record_attr_list : record_attr
{
$$ = list_make1($1);
}
| record_attr_list ',' record_attr
{
$$ = lappend($1, $3);
}
;
record_attr : attr_name decl_datatype decl_notnull decl_rec_defval
{
PLpgSQL_rec_attr *attr = NULL;
if($2->typoid == REFCURSOROID) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("ref cursor type nested by record is not supported yet."),
errdetail("Define record type of ref cursor type is not supported yet."),
errcause("feature not supported"),
erraction("check define of record type")));
u_sess->plsql_cxt.have_error = true;
}
attr = (PLpgSQL_rec_attr*)palloc0(sizeof(PLpgSQL_rec_attr));
attr->attrname = $1;
attr->type = $2;
attr->notnull = $3;
if ($4 != NULL)
{
if (attr->type->ttype == PLPGSQL_TTYPE_SCALAR)
attr->defaultvalue = $4;
else {
const char* message = "default value for row or record variable is not supported";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("default value for row or record variable is not supported"),
parser_errposition(@3)));
}
}
if ($3 && $4 == NULL) {
const char* message = "variables must have default value";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("variables declared as NOT NULL must have "
"a default value.")));
}
$$ = attr;
}
| attr_name record_var decl_notnull decl_rec_defval
{
#ifdef ENABLE_MULTIPLE_NODES
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or record type nesting is not supported in distributed database yet."),
errdetail("Define a record type of record is not supported in distributed database yet."),
errcause("feature not supported"),
erraction("check define of record type")));
u_sess->plsql_cxt.have_error = true;
#endif
if (IS_ANONYMOUS_BLOCK) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("array or record type nesting is not supported in anonymous block yet."),
errdetail("Define a record type of record is not supported yet."),
errcause("feature not supported"),
erraction("check define of record type")));
u_sess->plsql_cxt.have_error = true;
}
PLpgSQL_rec_attr *attr = NULL;
attr = (PLpgSQL_rec_attr*)palloc0(sizeof(PLpgSQL_rec_attr));
attr->attrname = $1;
attr->type = build_type_from_record_var($2);
attr->notnull = $3;
if ($4 != NULL)
{
if (attr->type->ttype == PLPGSQL_TTYPE_SCALAR)
attr->defaultvalue = $4;
else {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("default value for row or record variable is not supported"),
parser_errposition(@3)));
u_sess->plsql_cxt.have_error = true;
}
}
if ($3 && $4 == NULL) {
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("variables declared as NOT NULL must have "
"a default value.")));
u_sess->plsql_cxt.have_error = true;
}
$$ = attr;
}
| attr_name T_REFCURSOR decl_notnull decl_rec_defval
{
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmodule(MOD_PLSQL),
errmsg("ref cursor type nested by record is not supported yet."),
errdetail("Define a record type of ref cursor type is not supported yet."),
errcause("feature not supported"),
erraction("check define of record type")));
u_sess->plsql_cxt.have_error = true;
}
| attr_name varray_var decl_notnull decl_rec_defval
{
PLpgSQL_rec_attr *attr = NULL;
attr = (PLpgSQL_rec_attr*)palloc0(sizeof(PLpgSQL_rec_attr));
attr->attrname = $1;
PLpgSQL_type *var_type = ((PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$2])->datatype;
PLpgSQL_type *new_var_type = build_array_type_from_elemtype(var_type);
new_var_type->collectionType = var_type->collectionType;
new_var_type->tableOfIndexType = var_type->tableOfIndexType;
attr->type = new_var_type;
attr->notnull = $3;
if ($4 != NULL)
{
if (attr->type->ttype == PLPGSQL_TTYPE_SCALAR)
attr->defaultvalue = $4;
else {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("default value for row or record variable is not supported"),
parser_errposition(@3)));
u_sess->plsql_cxt.have_error = true;
}
}
if ($3 && $4 == NULL) {
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("variables declared as NOT NULL must have "
"a default value.")));
u_sess->plsql_cxt.have_error = true;
}
$$ = attr;
}
| attr_name table_var decl_notnull decl_rec_defval
{
PLpgSQL_rec_attr *attr = NULL;
attr = (PLpgSQL_rec_attr*)palloc0(sizeof(PLpgSQL_rec_attr));
attr->attrname = $1;
PLpgSQL_type *var_type = ((PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$2])->datatype;
PLpgSQL_type *new_var_type = build_array_type_from_elemtype(var_type);
new_var_type->collectionType = var_type->collectionType;
new_var_type->tableOfIndexType = var_type->tableOfIndexType;
attr->type = new_var_type;
attr->notnull = $3;
if ($4 != NULL)
{
if (attr->type->ttype == PLPGSQL_TTYPE_SCALAR)
attr->defaultvalue = $4;
else {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("default value for row or record variable is not supported"),
parser_errposition(@3)));
u_sess->plsql_cxt.have_error = true;
}
}
if ($3 && $4 == NULL) {
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("variables declared as NOT NULL must have "
"a default value.")));
u_sess->plsql_cxt.have_error = true;
}
$$ = attr;
}
;
opt_scrollable :
{
$$ = 0;
}
| K_NO K_SCROLL
{
$$ = CURSOR_OPT_NO_SCROLL;
}
| K_SCROLL
{
$$ = CURSOR_OPT_SCROLL;
}
;
decl_cursor_query :
{
int tok;
tok = yylex();
plpgsql_push_back_token(tok);
/* check cursor syntax, cursor query only accept select query */
{
$$ = read_sql_stmt("");
}
}
;
decl_cursor_args :
{
$$ = NULL;
}
| '(' decl_cursor_arglist ')'
{
PLpgSQL_row *newp;
int i;
ListCell *l;
newp = (PLpgSQL_row *)palloc0(sizeof(PLpgSQL_row));
newp->dtype = PLPGSQL_DTYPE_ROW;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->rowtupdesc = NULL;
newp->nfields = list_length($2);
newp->fieldnames = (char **)palloc(newp->nfields * sizeof(char *));
newp->varnos = (int *)palloc(newp->nfields * sizeof(int));
newp->isImplicit = true;
newp->addNamespace = false;
i = 0;
foreach (l, $2)
{
PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l);
newp->fieldnames[i] = arg->refname;
newp->varnos[i] = arg->dno;
i++;
}
list_free_ext($2);
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = (PLpgSQL_datum *) newp;
}
;
decl_cursor_arglist : decl_cursor_arg
{
$$ = list_make1($1);
}
| decl_cursor_arglist ',' decl_cursor_arg
{
$$ = lappend($1, $3);
}
;
decl_cursor_arg : decl_varname cursor_in_out_option decl_datatype
{
$$ = (PLpgSQL_datum *)
plpgsql_build_variable($1.name, $1.lineno,
$3, true);
pfree_ext($1.name);
}
;
cursor_in_out_option : K_IN |
K_OUT |
/* empty */
;
decl_is_for : K_IS | /* A db */
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) {
const char* message = "variables not exists";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("variable \"%s\" does not exist",
$1.ident),
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) {
int yylloc_bak = yylloc;
int isPkg =plpgsql_pkg_adddatum2ns($1.idents);
yylloc = yylloc_bak;
if (isPkg < 0) {
const char* message = "variables not exists";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("variable \"%s\" does not exist",
NameListToString($1.idents)),
parser_errposition(@1)));
}
}
$$ = nsi;
}
;
/*
* $$.name will do a strdup outer, so its original space should be free.
*/
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");
}
| 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");
}
| T_VARRAY
{
$$.name = pstrdup($1.ident);
$$.lineno = plpgsql_location_to_lineno(@1);
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
$1.ident, NULL, NULL,
NULL) != NULL)
yyerror("duplicate declaration");
}
| T_RECORD
{
$$.name = pstrdup($1.ident);
$$.lineno = plpgsql_location_to_lineno(@1);
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
$1.ident, NULL, NULL,
NULL) != NULL)
yyerror("duplicate declaration");
}
| T_TABLE
{
$$.name = pstrdup($1.ident);
$$.lineno = plpgsql_location_to_lineno(@1);
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
$1.ident, NULL, NULL,
NULL) != NULL)
yyerror("duplicate declaration");
}
| T_REFCURSOR
{
$$.name = pstrdup($1.ident);
$$.lineno = plpgsql_location_to_lineno(@1);
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
$1.ident, NULL, NULL,
NULL) != NULL)
yyerror("duplicate declaration");
}
| T_TABLE_VAR
{
if ($1.idents != NIL) {
yyerror("syntax error");
}
$$.name = pstrdup($1.ident);
$$.lineno = plpgsql_location_to_lineno(@1);
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
$1.ident, NULL, NULL,
NULL) != NULL)
yyerror("duplicate declaration");
}
| T_VARRAY_VAR
{
if ($1.idents != NIL || strcmp($1.ident, "bulk_exceptions") == 0) {
yyerror("syntax error");
}
$$.name = pstrdup($1.ident);
$$.lineno = plpgsql_location_to_lineno(@1);
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
$1.ident, NULL, NULL,
NULL) != NULL)
yyerror("duplicate declaration");
}
;
decl_const :
{ $$ = false; }
| K_CONSTANT
{ $$ = true; }
;
decl_datatype :
{
/*
* If there's a lookahead token, read_datatype
* should consume it.
*/
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
$$ = read_datatype(yychar);
yyclearin;
}
;
decl_collate :
{ $$ = InvalidOid; }
| K_COLLATE T_WORD
{
$$ = get_collation_oid(list_make1(makeString($2.ident)),
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_rec_defval :
{
$$ = NULL;
}
| decl_defkey
{
int tok;
$$ = read_sql_expression2(',', ')', ")", &tok);
plpgsql_push_back_token(tok);
}
;
decl_defkey : assign_operator
| K_DEFAULT
;
assign_operator : '='
| COLON_EQUALS
;
proc_sect :
{ $$ = NIL; }
| proc_stmts
{ $$ = $1; }
;
proc_stmts : proc_stmts proc_stmt
{
if ($2 == NULL)
$$ = $1;
else
$$ = lappend($1, $2);
}
| proc_stmt
{
if ($1 == NULL)
$$ = NIL;
else
$$ = list_make1($1);
}
;
/*
* For stmt_loop, stmt_while, stmt_for, stmt_foreach_a, is already labeled in
* existing MPPDB, so we reuse its labal to suport GOTO
*/
proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_loop
{ $$ = $1; }
| stmt_while
{ $$ = $1; }
| stmt_for
{ $$ = $1; }
| stmt_foreach_a
{ $$ = $1; }
| stmt_commit
{ $$ = $1; }
| stmt_rollback
{ $$ = $1; }
| label_stmts
{ $$ = $1; }
| stmt_savepoint
{ $$ = $1; }
;
goto_block_label :
{
$$ = NULL;
}
| LESS_LESS any_identifier GREATER_GREATER
{
plpgsql_ns_additem(PLPGSQL_NSTYPE_GOTO_LABEL, 0, "");
$$ = $2;
}
;
;
label_stmts: goto_block_label label_stmt
{
/*
* If label is not null, we record it in current execution
* block so that the later or former GOTO can redirect the plpgsql execution steps
*/
record_stmt_label($1, $2);
$$ = $2;
};
label_stmt : stmt_assign
{ $$ = $1; }
| stmt_if
{ $$ = $1; }
| stmt_case
{ $$ = $1; }
| stmt_exit
{ $$ = $1; }
| stmt_return
{ $$ = $1; }
| stmt_raise
{ $$ = $1; }
| stmt_execsql
{ $$ = $1; }
| stmt_dynexecute
{ $$ = $1; }
| stmt_perform
{ $$ = $1; }
| stmt_getdiag
{ $$ = $1; }
| stmt_goto
{ $$ = $1; }
| stmt_open
{ $$ = $1; }
| stmt_fetch
{ $$ = $1; }
| stmt_move
{ $$ = $1; }
| stmt_close
{ $$ = $1; }
| stmt_null
{ $$ = $1; }
;
stmt_perform : K_PERFORM {
#ifndef ENABLE_MULTIPLE_NODES
if (enable_out_param_override()) {
const char* message = "not support perform when behavior_compat_options=\"proc_outparam_override\"";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("not support perform when behavior_compat_options=\"proc_outparam_override\""),
parser_errposition(@1)));
}
#endif
} expr_until_semi
{
PLpgSQL_stmt_perform *newp;
newp = (PLpgSQL_stmt_perform *)palloc0(sizeof(PLpgSQL_stmt_perform));
newp->cmd_type = PLPGSQL_STMT_PERFORM;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->expr = $3;
newp->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *)newp;
}
;
stmt_assign : assign_var assign_operator expr_until_semi
{
PLpgSQL_stmt_assign *newp;
newp = (PLpgSQL_stmt_assign *)palloc0(sizeof(PLpgSQL_stmt_assign));
newp->cmd_type = PLPGSQL_STMT_ASSIGN;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->varno = $1;
newp->expr = $3;
newp->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *)newp;
}
| T_CWORD assign_operator expr_until_semi
{
cword_is_not_variable(&($1), @1);
}
;
stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
{
PLpgSQL_stmt_getdiag *newp;
ListCell *lc;
newp = (PLpgSQL_stmt_getdiag *)palloc0(sizeof(PLpgSQL_stmt_getdiag));
newp->cmd_type = PLPGSQL_STMT_GETDIAG;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->is_stacked = $2;
newp->diag_items = $4;
newp->sqlString = plpgsql_get_curline_query();
/*
* Check information items are valid for area option.
*/
foreach(lc, newp->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:
case PLPGSQL_GETDIAG_RESULT_OID:
if (newp->is_stacked) {
const char* message = "diagnostics item is not allowed in GET STACKED DIAGNOSTICS";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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_MESSAGE_TEXT:
if (!newp->is_stacked) {
const char* message = "diagnostics item is not allowed in GET CURRENT DIAGNOSTICS";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS",
plpgsql_getdiag_kindname(ditem->kind)),
parser_errposition(@1)));
}
break;
default:
const char* message = "unrecognized diagnostic item kind";
InsertErrorMessage(message, plpgsql_yylloc);
elog(errstate, "unrecognized diagnostic item kind: %d",
ditem->kind);
break;
}
}
$$ = (PLpgSQL_stmt *)newp;
}
;
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 *newp;
newp = (PLpgSQL_diag_item *)palloc(sizeof(PLpgSQL_diag_item));
newp->target = $1;
newp->kind = $3;
$$ = newp;
}
;
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_RESULT_OID, "result_oid"))
$$ = PLPGSQL_GETDIAG_RESULT_OID;
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_MESSAGE_TEXT, "message_text"))
$$ = PLPGSQL_GETDIAG_MESSAGE_TEXT;
else if (tok_is_keyword(tok, &yylval,
K_RETURNED_SQLSTATE, "returned_sqlstate"))
$$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
else
yyerror("unrecognized GET DIAGNOSTICS item");
}
;
getdiag_target : T_DATUM
{
check_assignable($1.datum, @1);
if ($1.datum->dtype == PLPGSQL_DTYPE_ROW ||
$1.datum->dtype == PLPGSQL_DTYPE_REC) {
const char* message = "not a scalar variable";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
NameOfDatum(&($1))),
parser_errposition(@1)));
}
$$ = $1.dno;
}
| 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);
}
;
table_var : T_TABLE
{
$$ = $1.dno;
AddNamespaceIfNeed($1.dno, $1.ident);
}
;
varray_var : T_VARRAY
{
$$ = $1.dno;
AddNamespaceIfNeed($1.dno, $1.ident);
}
;
record_var : T_RECORD
{
$$ = $1.dno;
AddNamespaceIfNeed($1.dno, $1.ident);
}
;
assign_var
: T_RECORD assign_list
{
check_assignable($1.datum, @1);
if ($2 == NIL) {
$$ = $1.dno;
} else {
if(IsA((Node*)linitial($2), A_Indices) && list_length($2) == 1) {
PLpgSQL_arrayelem *newp;
newp = (PLpgSQL_arrayelem *)palloc0(sizeof(PLpgSQL_arrayelem));
newp->dtype = PLPGSQL_DTYPE_ARRAYELEM;
newp->subscript = (PLpgSQL_expr*)(((A_Indices*)linitial($2))->uidx);
newp->arrayparentno = $1.dno;
/* initialize cached type data to "not valid" */
newp->parenttypoid = InvalidOid;
newp->assignattrno = -1;
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = newp->dno;
} else {
PLpgSQL_assignlist *newp;
newp = (PLpgSQL_assignlist *)palloc0(sizeof(PLpgSQL_assignlist));
newp->dtype = PLPGSQL_DTYPE_ASSIGNLIST;
newp->assignlist = $2;
newp->targetno = $1.dno;
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = newp->dno;
}
}
}
| T_VARRAY_VAR assign_list
{
check_assignable($1.datum, @1);
if ($2 == NIL) {
$$ = $1.dno;
} else {
if(IsA((Node*)linitial($2), A_Indices) && list_length($2) == 1) {
PLpgSQL_arrayelem *newp;
newp = (PLpgSQL_arrayelem *)palloc0(sizeof(PLpgSQL_arrayelem));
newp->dtype = PLPGSQL_DTYPE_ARRAYELEM;
newp->subscript = (PLpgSQL_expr*)(((A_Indices*)linitial($2))->uidx);
newp->arrayparentno = $1.dno;
/* initialize cached type data to "not valid" */
newp->parenttypoid = InvalidOid;
newp->assignattrno = -1;
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = newp->dno;
} else {
PLpgSQL_assignlist *newp;
newp = (PLpgSQL_assignlist *)palloc0(sizeof(PLpgSQL_assignlist));
newp->dtype = PLPGSQL_DTYPE_ASSIGNLIST;
newp->assignlist = $2;
newp->targetno = $1.dno;
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = newp->dno;
}
}
}
| T_TABLE_VAR assign_list
{
check_assignable($1.datum, @1);
if ($1.datum->dtype == PLPGSQL_DTYPE_VAR) {
check_autonomous_nest_tablevar((PLpgSQL_var*)$1.datum);
}
if ($2 == NIL) {
$$ = $1.dno;
} else {
if(IsA((Node*)linitial($2), A_Indices) && list_length($2) == 1) {
PLpgSQL_tableelem *newp;
newp = (PLpgSQL_tableelem *)palloc0(sizeof(PLpgSQL_tableelem));
newp->dtype = PLPGSQL_DTYPE_TABLEELEM;
newp->subscript = (PLpgSQL_expr*)(((A_Indices*)linitial($2))->uidx);
newp->tableparentno = $1.dno;
/* initialize cached type data to "not valid" */
newp->tabletypoid = InvalidOid;
newp->assignattrno = -1;
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = newp->dno;
} else {
PLpgSQL_assignlist *newp;
newp = (PLpgSQL_assignlist *)palloc0(sizeof(PLpgSQL_assignlist));
newp->dtype = PLPGSQL_DTYPE_ASSIGNLIST;
newp->assignlist = $2;
newp->targetno = $1.dno;
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = newp->dno;
}
}
}
| T_DATUM assign_list
{
check_assignable($1.datum, @1);
if ($2 == NIL) {
check_record_nest_tableof_index($1.datum);
$$ = $1.dno;
} else {
if(IsA((Node*)linitial($2), A_Indices) && list_length($2) == 1) {
PLpgSQL_datum* target = u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$1.dno];
if (target->dtype == PLPGSQL_DTYPE_VAR &&
((PLpgSQL_var*)target)->datatype->collectionType == PLPGSQL_COLLECTION_TABLE) {
PLpgSQL_tableelem *newp;
newp = (PLpgSQL_tableelem *)palloc0(sizeof(PLpgSQL_tableelem));
newp->dtype = PLPGSQL_DTYPE_TABLEELEM;
newp->subscript = (PLpgSQL_expr*)(((A_Indices*)linitial($2))->uidx);
newp->tableparentno = $1.dno;
/* initialize cached type data to "not valid" */
newp->tabletypoid = InvalidOid;
newp->assignattrno = -1;
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = newp->dno;
} else {
PLpgSQL_arrayelem *newp;
newp = (PLpgSQL_arrayelem *)palloc0(sizeof(PLpgSQL_arrayelem));
newp->dtype = PLPGSQL_DTYPE_ARRAYELEM;
newp->subscript = (PLpgSQL_expr*)(((A_Indices*)linitial($2))->uidx);
newp->arrayparentno = $1.dno;
/* initialize cached type data to "not valid" */
newp->parenttypoid = InvalidOid;
newp->assignattrno = -1;
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = newp->dno;
}
} else {
PLpgSQL_assignlist *newp;
newp = (PLpgSQL_assignlist *)palloc0(sizeof(PLpgSQL_assignlist));
newp->dtype = PLPGSQL_DTYPE_ASSIGNLIST;
newp->targetno = $1.dno;
newp->assignlist = $2;
plpgsql_adddatum((PLpgSQL_datum *) newp);
$$ = newp->dno;
}
}
}
| T_PACKAGE_VARIABLE assign_list
{
check_assignable($1.datum, @1);
if ($2 != NIL) {
PLpgSQL_assignlist *newptr;
newptr = (PLpgSQL_assignlist *)palloc0(sizeof(PLpgSQL_assignlist));
newptr->dtype = PLPGSQL_DTYPE_ASSIGNLIST;
newptr->targetno = $1.dno;
newptr->assignlist = $2;
plpgsql_adddatum((PLpgSQL_datum *) newptr);
$$ = newptr->dno;
} else {
check_record_nest_tableof_index($1.datum);
$$ = $1.dno;
}
}
;
assign_list : /*EMPTY*/ {$$ = NIL; }
| assign_list assign_el {$$ = lappend($1, $2);}
;
assign_el : '.' attr_name
{
$$ = (Node *) makeString($2);
}
| '(' expr_until_parenthesis
{
A_Indices *ai = makeNode(A_Indices);
ai->lidx = NULL;
ai->uidx = (Node *)$2;
$$ = (Node *)ai;
}
| '[' expr_until_rightbracket
{
A_Indices *ai = makeNode(A_Indices);
ai->lidx = NULL;
ai->uidx = (Node *)$2;
$$ = (Node *)ai;
}
;
attr_name
:
{
$$ = get_attrname(yychar);
yyclearin;
}
;
stmt_goto : K_GOTO label_name
{
PLpgSQL_stmt_goto *newp;
newp = (PLpgSQL_stmt_goto *)palloc0(sizeof(PLpgSQL_stmt_goto));
newp->cmd_type = PLPGSQL_STMT_GOTO;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->label = $2;
newp->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *)newp;
}
;
label_name : T_WORD ';'
{
$$ = $1.ident;
}
;
stmt_if : K_IF expr_until_then proc_sect stmt_elsifs stmt_else K_END K_IF ';'
{
PLpgSQL_stmt_if *newp;
newp = (PLpgSQL_stmt_if *)palloc0(sizeof(PLpgSQL_stmt_if));
newp->cmd_type = PLPGSQL_STMT_IF;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->cond = $2;
newp->then_body = $3;
newp->elsif_list = $4;
newp->else_body = $5;
newp->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *)newp;
}
;
stmt_elsifs :
{
$$ = NIL;
}
| stmt_elsifs K_ELSIF expr_until_then proc_sect
{
PLpgSQL_if_elsif *newp;
newp = (PLpgSQL_if_elsif *)palloc0(sizeof(PLpgSQL_if_elsif));
newp->lineno = plpgsql_location_to_lineno(@2);
newp->cond = $3;
newp->stmts = $4;
$$ = lappend($1, newp);
}
;
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 *newp = (PLpgSQL_case_when *)palloc(sizeof(PLpgSQL_case_when));
newp->lineno = plpgsql_location_to_lineno(@1);
newp->expr = $2;
newp->stmts = $3;
newp->sqlString = plpgsql_get_curline_query();
$$ = newp;
}
;
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_block_label K_LOOP loop_body
{
PLpgSQL_stmt_loop *newp;
newp = (PLpgSQL_stmt_loop *)palloc0(sizeof(PLpgSQL_stmt_loop));
newp->cmd_type = PLPGSQL_STMT_LOOP;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->label = $1;
newp->body = $3.stmts;
newp->sqlString = plpgsql_get_curline_query();
check_labels($1, $3.end_label, $3.end_label_location);
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)newp;
/* register the stmt if it is labeled */
record_stmt_label($1, (PLpgSQL_stmt *)newp);
}
;
stmt_while : opt_block_label K_WHILE expr_until_loop loop_body
{
PLpgSQL_stmt_while *newp;
newp = (PLpgSQL_stmt_while *)palloc0(sizeof(PLpgSQL_stmt_while));
newp->cmd_type = PLPGSQL_STMT_WHILE;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->label = $1;
newp->cond = $3;
newp->body = $4.stmts;
newp->sqlString = plpgsql_get_curline_query();
check_labels($1, $4.end_label, $4.end_label_location);
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)newp;
/* register the stmt if it is labeled */
record_stmt_label($1, (PLpgSQL_stmt *)newp);
}
;
stmt_for : opt_block_label K_FOR for_control loop_body
{
/* This runs after we've scanned the loop body */
if ($3->cmd_type == PLPGSQL_STMT_FORI)
{
PLpgSQL_stmt_fori *newp;
newp = (PLpgSQL_stmt_fori *) $3;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->label = $1;
newp->body = $4.stmts;
newp->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *) newp;
/* register the stmt if it is labeled */
record_stmt_label($1, (PLpgSQL_stmt *)newp);
}
else
{
PLpgSQL_stmt_forq *newp;
AssertEreport($3->cmd_type == PLPGSQL_STMT_FORS ||
$3->cmd_type == PLPGSQL_STMT_FORC ||
$3->cmd_type == PLPGSQL_STMT_DYNFORS,
MOD_PLSQL,
"unexpected node type.");
/* forq is the common supertype of all three */
newp = (PLpgSQL_stmt_forq *) $3;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->label = $1;
newp->body = $4.stmts;
newp->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *) newp;
/* register the stmt if it is labeled */
record_stmt_label($1, (PLpgSQL_stmt *)newp);
}
check_labels($1, $4.end_label, $4.end_label_location);
/* close namespace started in opt_block_label */
plpgsql_ns_pop();
}
| opt_block_label K_FORALL forall_control opt_save_exceptions forall_body
{
/* This runs after we've scanned the loop body */
/* A db FORALL support 3 types like below. We implemented the first one.
* FORALL index_name IN lower_bound .. upper_bound
* FORALL index_name IN INDICES OF collection between lower_bound and upper_bound
* FORALL index_name IN VALUES OF index_collection
* forall_body can only have one statement.
*/
if ($3->cmd_type == PLPGSQL_STMT_FORI)
{
PLpgSQL_stmt_fori *newm;
newm = (PLpgSQL_stmt_fori *) $3;
newm->lineno = plpgsql_location_to_lineno(@2);
newm->label = NULL;
newm->save_exceptions = $4;
newm->body = $5;
newm->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *) newm;
}
else {
const char* message = "please use \'FORALL index_name IN lower_bound .. upper_bound\'";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("please use \'FORALL index_name IN lower_bound .. upper_bound\'")));
}
/* close namespace started in opt_block_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 *newp;
PLpgSQL_expr *expr;
int term;
expr = read_sql_expression2(K_LOOP, K_USING,
"LOOP or USING",
&term);
newp = (PLpgSQL_stmt_dynfors *)palloc0(sizeof(PLpgSQL_stmt_dynfors));
newp->cmd_type = PLPGSQL_STMT_DYNFORS;
if ($1.rec)
{
newp->rec = $1.rec;
check_assignable((PLpgSQL_datum *) newp->rec, @1);
}
else if ($1.row)
{
newp->row = $1.row;
check_assignable((PLpgSQL_datum *) newp->row, @1);
}
else if ($1.scalar)
{
/* convert single scalar to list */
newp->row = make_scalar_list1($1.name, $1.scalar, $1.dno,
$1.lineno, @1);
/* no need for check_assignable */
}
else
{
const char* message = "loop variable of loop over rows must be a record or row variable or list of scalar variables";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("loop variable of loop over rows must be a record or row variable or list of scalar variables"),
parser_errposition(@1)));
}
newp->query = expr;
if (term == K_USING)
{
do
{
expr = read_sql_expression2(',', K_LOOP,
", or LOOP",
&term);
newp->params = lappend(newp->params, expr);
} while (term == ',');
}
$$ = (PLpgSQL_stmt *) newp;
}
else if ((tok == T_DATUM || tok == T_PACKAGE_VARIABLE) &&
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR &&
((PLpgSQL_var *) yylval.wdatum.datum)->datatype->typoid == REFCURSOROID)
{
/* It's FOR var IN cursor */
PLpgSQL_stmt_forc *newp;
PLpgSQL_var *cursor = (PLpgSQL_var *) yylval.wdatum.datum;
newp = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc));
newp->cmd_type = PLPGSQL_STMT_FORC;
newp->curvar = yylval.wdatum.dno;
/* Should have had a single variable name */
if ($1.scalar && $1.row) {
const char* message = "cursor FOR loop must have only one target variable";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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) {
const char* message = "cursor FOR loop must use a bound cursor variable";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cursor FOR loop must use a bound cursor variable"),
parser_errposition(tokloc)));
}
/* collect cursor's parameters if any */
newp->argquery = read_cursor_args(cursor,
K_LOOP,
"LOOP");
/* create loop's private RECORD variable */
newp->rec = plpgsql_build_record($1.name,
$1.lineno,
true);
$$ = (PLpgSQL_stmt *) newp;
}
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 *newp;
/* 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) {
const char* message = "integer FOR loop must have only one target variable";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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),
true);
newp = (PLpgSQL_stmt_fori *)palloc0(sizeof(PLpgSQL_stmt_fori));
newp->cmd_type = PLPGSQL_STMT_FORI;
newp->var = fvar;
newp->reverse = reverse;
newp->lower = expr1;
newp->upper = expr2;
newp->step = expr_by;
$$ = (PLpgSQL_stmt *) newp;
}
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 *newp;
if (reverse) {
const char* message = "cannot specify REVERSE in query FOR loop";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot specify REVERSE in query FOR loop"),
parser_errposition(tokloc)));
}
AssertEreport(strncmp(expr1->query, "SELECT ", 7) == 0,
MOD_PLSQL,
"It should be SELECT");
tmp_query = pstrdup(expr1->query + 7);
pfree_ext(expr1->query);
expr1->query = tmp_query;
check_sql_expr(expr1->query, expr1loc, 0);
newp = (PLpgSQL_stmt_fors *)palloc0(sizeof(PLpgSQL_stmt_fors));
newp->cmd_type = PLPGSQL_STMT_FORS;
if ($1.rec)
{
#ifndef ENABLE_MULTIPLE_NODES
/* only A format and not in upgrade, IMPLICIT_FOR_LOOP_VARIABLE is valid */
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT
&& IMPLICIT_FOR_LOOP_VARIABLE
&& u_sess->attr.attr_common.upgrade_mode == 0) {
BuildForQueryVariable(expr1, &newp->row, &newp->rec, $1.name, $1.lineno);
check_assignable((PLpgSQL_datum *)newp->rec ?
(PLpgSQL_datum *)newp->rec : (PLpgSQL_datum *)newp->row, @1);
} else {
/* check the sql */
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && ALLOW_PROCEDURE_COMPILE_CHECK) {
(void)getCursorTupleDesc(expr1, false, true);
}
newp->rec = $1.rec;
check_assignable((PLpgSQL_datum *) newp->rec, @1);
}
#else
newp->rec = $1.rec;
check_assignable((PLpgSQL_datum *) newp->rec, @1);
#endif
}
else if ($1.row)
{
#ifndef ENABLE_MULTIPLE_NODES
/* only A format and not in upgrade, IMPLICIT_FOR_LOOP_VARIABLE is valid */
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT
&& IMPLICIT_FOR_LOOP_VARIABLE
&& u_sess->attr.attr_common.upgrade_mode == 0) {
BuildForQueryVariable(expr1, &newp->row, &newp->rec, $1.name, $1.lineno);
check_assignable((PLpgSQL_datum *)newp->rec ?
(PLpgSQL_datum *)newp->rec : (PLpgSQL_datum *)newp->row, @1);
} else {
/* check the sql */
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && ALLOW_PROCEDURE_COMPILE_CHECK) {
(void)getCursorTupleDesc(expr1, false, true);
}
newp->row = $1.row;
check_assignable((PLpgSQL_datum *) newp->row, @1);
}
#else
newp->row = $1.row;
check_assignable((PLpgSQL_datum *) newp->row, @1);
#endif
}
else if ($1.scalar)
{
#ifndef ENABLE_MULTIPLE_NODES
/* only A format and not in upgrade, IMPLICIT_FOR_LOOP_VARIABLE is valid */
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT
&& IMPLICIT_FOR_LOOP_VARIABLE
&& u_sess->attr.attr_common.upgrade_mode == 0) {
BuildForQueryVariable(expr1, &newp->row, &newp->rec, $1.name, $1.lineno);
check_assignable((PLpgSQL_datum *)newp->rec ?
(PLpgSQL_datum *)newp->rec : (PLpgSQL_datum *)newp->row, @1);
} else {
/* check the sql */
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && ALLOW_PROCEDURE_COMPILE_CHECK) {
(void)getCursorTupleDesc(expr1, false, true);
}
/* convert single scalar to list */
newp->row = make_scalar_list1($1.name, $1.scalar, $1.dno, $1.lineno, @1);
/* no need for check_assignable */
}
#else
/* convert single scalar to list */
newp->row = make_scalar_list1($1.name, $1.scalar, $1.dno, $1.lineno, @1);
/* no need for check_assignable */
#endif
}
else
{
#ifndef ENABLE_MULTIPLE_NODES
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && ALLOW_PROCEDURE_COMPILE_CHECK) {
BuildForQueryVariable(expr1, &newp->row, &newp->rec, $1.name, $1.lineno);
check_assignable((PLpgSQL_datum *)newp->rec ?
(PLpgSQL_datum *)newp->rec : (PLpgSQL_datum *)newp->row, @1);
} else {
PLpgSQL_type dtype;
dtype.ttype = PLPGSQL_TTYPE_REC;
newp->rec = (PLpgSQL_rec *)
plpgsql_build_variable($1.name,$1.lineno, &dtype, true);
check_assignable((PLpgSQL_datum *) newp->rec, @1);
}
#else
PLpgSQL_type dtype;
dtype.ttype = PLPGSQL_TTYPE_REC;
newp->rec = (PLpgSQL_rec *)
plpgsql_build_variable($1.name,$1.lineno, &dtype, true);
check_assignable((PLpgSQL_datum *) newp->rec, @1);
#endif
}
newp->query = expr1;
$$ = (PLpgSQL_stmt *) newp;
}
}
}
;
forall_control :for_variable K_IN
{
int tok;
int expr1loc;
PLpgSQL_expr *expr1;
/*
* 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,
0,
0,
"..",
"SELECT ",
true,
false,
true,
&expr1loc,
&tok);
if (DOT_DOT == tok)
{
/* Saw "..", so it must be an integer loop */
PLpgSQL_expr *expr2 = NULL;
PLpgSQL_var *fvar = NULL;
PLpgSQL_stmt_fori *newm = NULL;
/* Check first expression is well-formed */
check_sql_expr(expr1->query, expr1loc, 7);
/* Read and check the second one */
expr2 = read_sql_construct6(K_MERGE,
K_INSERT,
K_SELECT,
K_UPDATE,
K_DELETE,
K_SAVE,
"DML",
"SELECT ",
true,
false,
true,
NULL,
&tok);
plpgsql_push_back_token(tok);
if (';' == tok) {
const char* message = "FORALL must follow DML statement.";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_FORALL_NEED_DML),
errmsg("FORALL must follow DML statement.")));
}
/* should follow DML statement */
if (tok != K_INSERT && tok != K_UPDATE && tok != K_DELETE && tok != K_SELECT && tok != K_MERGE && tok != K_SAVE) {
const char* message = "FORALL must follow DML statement.";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_FORALL_NEED_DML),
errmsg("FORALL must follow DML statement.")));
}
if (tok == K_SAVE) {
CheckSaveExceptionsDML(errstate);
}
/* Should have had a single variable name */
if ($1.scalar && $1.row) {
const char* message = "integer FORALL must have just one target variable.";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("integer FORALL must have just one target variable")));
}
/* create loop's private variable */
fvar = (PLpgSQL_var *)
plpgsql_build_variable($1.name,
$1.lineno,
plpgsql_build_datatype(INT4OID, -1, InvalidOid),
true);
newm = (PLpgSQL_stmt_fori *)palloc0(sizeof(PLpgSQL_stmt_fori));
newm->cmd_type = PLPGSQL_STMT_FORI;
newm->var = fvar;
newm->reverse = false;
newm->lower = expr1;
newm->upper = expr2;
newm->step = NULL;
$$ = (PLpgSQL_stmt *) newm;
}
else {
const char* message = "please use \'FORALL index_name IN lower_bound .. upper_bound\'";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("please use \'FORALL index_name IN lower_bound .. upper_bound\'")));
}
}
;
opt_save_exceptions : K_SAVE K_EXCEPTIONS
{
#ifndef ENABLE_MULTIPLE_NODES
$$ = true;
#else
const char* message = "SAVE EXCEPTIONS is not supported";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("%s.", message)));
$$ = false;
#endif
}
|
{
$$ = false;
}
/*
* 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)
{
$$.scalar = NULL;
$$.rec = NULL;
$$.row = (PLpgSQL_row *) $1.datum;
$$.dno = $1.dno;
}
else if ($1.datum->dtype == PLPGSQL_DTYPE_RECORD)
{
$$.scalar = NULL;
$$.rec = NULL;
$$.row = (PLpgSQL_row *) $1.datum;
$$.dno = $1.dno;
}
else if ($1.datum->dtype == PLPGSQL_DTYPE_REC)
{
$$.scalar = NULL;
$$.rec = (PLpgSQL_rec *) $1.datum;
$$.row = NULL;
$$.dno = $1.dno;
}
else
{
int tok;
$$.scalar = $1.datum;
$$.rec = NULL;
$$.row = NULL;
$$.dno = $1.dno;
/* check for comma-separated list */
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == ',')
$$.row = read_into_scalar_list($$.name,
$$.scalar,
$$.dno,
@1);
}
}
| T_VARRAY_VAR
{
$$.name = NameOfDatum(&($1));
$$.lineno = plpgsql_location_to_lineno(@1);
if ($1.datum->dtype == PLPGSQL_DTYPE_ROW)
{
$$.scalar = NULL;
$$.rec = NULL;
$$.row = (PLpgSQL_row *) $1.datum;
$$.dno = $1.dno;
}
else if ($1.datum->dtype == PLPGSQL_DTYPE_REC)
{
$$.scalar = NULL;
$$.rec = (PLpgSQL_rec *) $1.datum;
$$.row = NULL;
$$.dno = $1.dno;
}
else
{
int tok;
$$.scalar = $1.datum;
$$.rec = NULL;
$$.row = NULL;
$$.dno = $1.dno;
/* check for comma-separated list */
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == ',')
$$.row = read_into_scalar_list($$.name,
$$.scalar,
$$.dno,
@1);
}
}
| T_TABLE_VAR
{
$$.name = NameOfDatum(&($1));
$$.lineno = plpgsql_location_to_lineno(@1);
if ($1.datum->dtype == PLPGSQL_DTYPE_ROW)
{
$$.scalar = NULL;
$$.rec = NULL;
$$.row = (PLpgSQL_row *) $1.datum;
$$.dno = $1.dno;
}
else if ($1.datum->dtype == PLPGSQL_DTYPE_REC)
{
$$.scalar = NULL;
$$.rec = (PLpgSQL_rec *) $1.datum;
$$.row = NULL;
$$.dno = $1.dno;
}
else
{
int tok;
$$.scalar = $1.datum;
if ($1.datum->dtype == PLPGSQL_DTYPE_VAR) {
check_autonomous_nest_tablevar((PLpgSQL_var*)$1.datum);
}
$$.rec = NULL;
$$.row = NULL;
$$.dno = $1.dno;
/* check for comma-separated list */
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == ',')
$$.row = read_into_scalar_list($$.name,
$$.scalar,
$$.dno,
@1);
}
}
| T_WORD
{
int tok;
$$.name = $1.ident;
$$.lineno = plpgsql_location_to_lineno(@1);
$$.scalar = NULL;
$$.rec = 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_block_label K_FOREACH for_variable foreach_slice K_IN K_ARRAY expr_until_loop loop_body
{
PLpgSQL_stmt_foreach_a *newp;
newp = (PLpgSQL_stmt_foreach_a *)palloc0(sizeof(PLpgSQL_stmt_foreach_a));
newp->cmd_type = PLPGSQL_STMT_FOREACH_A;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->label = $1;
newp->slice = $4;
newp->expr = $7;
newp->body = $8.stmts;
newp->sqlString = plpgsql_get_curline_query();
if ($3.rec)
{
newp->varno = $3.dno;
check_assignable((PLpgSQL_datum *) $3.rec, @3);
}
else if ($3.row)
{
if ($3.scalar) {
newp->varno = $3.row->dno;
} else {
newp->varno = $3.dno;
}
check_assignable((PLpgSQL_datum *) $3.row, @3);
}
else if ($3.scalar)
{
newp->varno = $3.dno;
check_assignable($3.scalar, @3);
}
else
{
const char* message = "loop variable of FOREACH must be a known variable or list of variables";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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 *) newp;
/* register the stmt if it is labeled */
record_stmt_label($1, (PLpgSQL_stmt *)newp);
}
;
foreach_slice :
{
$$ = 0;
}
| K_SLICE ICONST
{
$$ = $2;
}
;
forall_body : stmt_dynexecute
{ $$ = list_make1($1); }
| stmt_execsql
{ $$ = list_make1($1); }
;
stmt_exit : exit_type opt_label opt_exitcond
{
PLpgSQL_stmt_exit *newp;
newp = (PLpgSQL_stmt_exit *)palloc0(sizeof(PLpgSQL_stmt_exit));
newp->cmd_type = PLPGSQL_STMT_EXIT;
newp->is_exit = $1;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->label = $2;
newp->cond = $3;
newp->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *)newp;
}
;
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"))
{
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
$$ = make_return_next_stmt(@1);
}
else if (tok_is_keyword(tok, &yylval,
K_QUERY, "query"))
{
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
$$ = make_return_query_stmt(@1);
}
else
{
plpgsql_push_back_token(tok);
$$ = make_return_stmt(@1);
}
}
;
stmt_raise : K_RAISE
{
PLpgSQL_stmt_raise *newp;
int tok;
PLpgSQL_row * row = NULL;
char message[32] = "";
StringInfoData ds;
initStringInfo(&ds);
newp = (PLpgSQL_stmt_raise *)palloc(sizeof(PLpgSQL_stmt_raise));
newp->cmd_type = PLPGSQL_STMT_RAISE;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->elog_level = ERROR; /* default */
newp->condname = NULL;
newp->message = NULL;
newp->params = NIL;
newp->options = NIL;
newp->sqlString = plpgsql_get_curline_query();
newp->hasExceptionInit = false;
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 != ';')
{
if (T_CWORD == tok) {
yyerror("not found package");
}
if ((T_DATUM == tok || T_PACKAGE_VARIABLE == tok) && PLPGSQL_DTYPE_ROW == yylval.wdatum.datum->dtype)
{
PLpgSQL_expr *expr = NULL;
errno_t rc = EOK;
rc = sprintf_s(message, sizeof(message), "line:%d ", plpgsql_location_to_lineno(@1));
securec_check_ss(rc, "\0", "\0");
appendStringInfoString(&ds, message);
appendStringInfoString(&ds,"%");
row = (PLpgSQL_row *)yylval.wdatum.datum;
/* condname is system embedded error name, so it is still null in this case. */
newp->hasExceptionInit = row->hasExceptionInit;
newp->condname = pstrdup(plpgsql_get_sqlstate(row->customErrorCode));
newp->message = pstrdup(ds.data);
plpgsql_push_back_token(tok);
expr = read_sql_construct(';', 0, 0, ";",
"SELECT ", true, true, true, NULL, &tok);
if (tok != ';')
yyerror("syntax error");
newp->params = lappend(newp->params, expr);
}
else
{
/*
* First is an optional elog severity level.
*/
if (tok_is_keyword(tok, &yylval,
K_EXCEPTION, "exception"))
{
newp->elog_level = ERROR;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_WARNING, "warning"))
{
newp->elog_level = WARNING;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_NOTICE, "notice"))
{
newp->elog_level = NOTICE;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_INFO, "info"))
{
newp->elog_level = INFO;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_LOG, "log"))
{
newp->elog_level = LOG;
tok = yylex();
}
else if (tok_is_keyword(tok, &yylval,
K_DEBUG, "debug"))
{
newp->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 */
newp->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);
newp->params = lappend(newp->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");
newp->condname = sqlstatestr;
}
else
{
if (tok != T_WORD)
yyerror("syntax error");
newp->condname = yylval.word.ident;
plpgsql_recognize_err_condition(newp->condname,
false);
}
tok = yylex();
if (tok != ';' && tok != K_USING)
yyerror("syntax error");
}
if (tok == K_USING)
newp->options = read_raise_options();
}
}
pfree_ext(ds.data);
$$ = (PLpgSQL_stmt *)newp;
}
;
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_ALTER
{
$$ = make_execsql_stmt(K_ALTER, @1);
}
| K_INSERT
{
$$ = make_execsql_stmt(K_INSERT, @1);
}
| K_SELECT /* DML:select */
{
int tok = -1;
tok = yylex();
plpgsql_push_back_token(tok);
$$ = make_execsql_stmt(K_SELECT, @1);
}
| K_UPDATE /* DML:insert */
{
int tok = -1;
tok = yylex();
plpgsql_push_back_token(tok);
$$ = make_execsql_stmt(K_UPDATE, @1);
}
| K_DELETE /* DML:delete */
{
int tok = -1;
tok = yylex();
plpgsql_push_back_token(tok);
$$ = make_execsql_stmt(K_DELETE, @1);
}
| K_WITH
{
$$ = make_execsql_stmt(K_WITH, @1);
}
| K_MERGE
{
$$ = make_execsql_stmt(K_MERGE, @1);
}
| T_WORD /*C-style function call */
{
int tok = -1;
bool isCallFunc = false;
bool FuncNoarg = false;
if (0 == strcasecmp($1.ident, "DBMS_LOB")
&& (plpgsql_is_token_match2('.', K_OPEN)
|| plpgsql_is_token_match2('.', K_CLOSE)))
$$ = parse_lob_open_close(@1);
else
{
tok = yylex();
if ('(' == tok)
isCallFunc = is_function($1.ident, false, false);
else if ('=' ==tok || COLON_EQUALS == tok || '[' == tok)
word_is_not_variable(&($1), @1);
else if (';' == tok)
{
isCallFunc = is_function($1.ident, false, true);
FuncNoarg = true;
}
plpgsql_push_back_token(tok);
if(isCallFunc)
{
if (FuncNoarg)
{
$$ = make_callfunc_stmt_no_arg($1.ident, @1);
}
else
{
PLpgSQL_stmt *stmt = make_callfunc_stmt($1.ident, @1, false, false);
if (stmt->cmd_type == PLPGSQL_STMT_PERFORM)
{
((PLpgSQL_stmt_perform *)stmt)->expr->is_funccall = true;
}
else if (stmt->cmd_type == PLPGSQL_STMT_EXECSQL)
{
((PLpgSQL_stmt_execsql *)stmt)->sqlstmt->is_funccall = true;
}
$$ = stmt;
}
}
else
{
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
$$ = make_execsql_stmt(T_WORD, @1);
}
}
}
| unreserved_keyword_func
{
int tok = -1;
bool isCallFunc = false;
bool FuncNoarg = false;
tok = yylex();
if ('(' == tok)
isCallFunc = is_function($1, false, false);
else if (';' == tok)
{
isCallFunc = is_function($1, false, true);
FuncNoarg = true;
}
plpgsql_push_back_token(tok);
if(isCallFunc)
{
if (FuncNoarg)
{
$$ = make_callfunc_stmt_no_arg($1, @1);
}
else
{
PLpgSQL_stmt *stmt = make_callfunc_stmt($1, @1, false, false);
if (stmt == NULL)
{
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
}
else
{
if (stmt->cmd_type == PLPGSQL_STMT_PERFORM)
{
((PLpgSQL_stmt_perform *)stmt)->expr->is_funccall = true;
}
else if (stmt->cmd_type == PLPGSQL_STMT_EXECSQL)
{
((PLpgSQL_stmt_execsql *)stmt)->sqlstmt->is_funccall = true;
}
$$ = stmt;
}
}
}
else
{
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
}
}
| T_CWORD '('
{
char *name = NULL;
bool isCallFunc = false;
bool FuncNoarg = false;
checkFuncName($1.idents);
MemoryContext colCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_tmp_cxt);
name = NameListToString($1.idents);
(void)MemoryContextSwitchTo(colCxt);
isCallFunc = is_function(name, false, false, $1.idents);
if (isCallFunc)
{
if (FuncNoarg)
$$ = make_callfunc_stmt_no_arg(name, @1, false, $1.idents);
else
{
PLpgSQL_stmt *stmt = make_callfunc_stmt(name, @1, false, true, $1.idents);
if (stmt == NULL) {
const char* message = "call func stmt was null";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
errmsg("call func stmt was null"),
errdetail("call func stmt was null, you can try to recompile the function")));
}
if (stmt->cmd_type == PLPGSQL_STMT_PERFORM)
{
((PLpgSQL_stmt_perform *)stmt)->expr->is_funccall = true;
}
else if (stmt->cmd_type == PLPGSQL_STMT_EXECSQL)
{
((PLpgSQL_stmt_execsql *)stmt)->sqlstmt->is_funccall = true;
}
$$ = stmt;
}
}
else
{
$$ = make_execsql_stmt(T_CWORD, @1);
}
}
| T_CWORD ';'
{
char *name = NULL;
bool isCallFunc = false;
checkFuncName($1.idents);
MemoryContext colCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_tmp_cxt);
name = NameListToString($1.idents);
(void)MemoryContextSwitchTo(colCxt);
isCallFunc = is_function(name, false, true, $1.idents);
bool FuncNoarg = true;
if (isCallFunc)
{
if (FuncNoarg)
$$ = make_callfunc_stmt_no_arg(name, @1, true, $1.idents);
else
{
PLpgSQL_stmt *stmt = make_callfunc_stmt(name, @1, false, true, $1.idents);
if (stmt->cmd_type == PLPGSQL_STMT_PERFORM)
{
((PLpgSQL_stmt_perform *)stmt)->expr->is_funccall = true;
}
else if (stmt->cmd_type == PLPGSQL_STMT_EXECSQL)
{
((PLpgSQL_stmt_execsql *)stmt)->sqlstmt->is_funccall = true;
}
$$ = stmt;
}
}
else
{
$$ = make_execsql_stmt(T_CWORD, @1);
}
}
| T_ARRAY_TRIM
{
check_table_index(yylval.wdatum.datum, "trim");
int dno = yylval.wdatum.dno;
StringInfoData sqlBuf;
initStringInfo(&sqlBuf);
appendStringInfo(&sqlBuf, "array_trim(");
CastArrayNameToArrayFunc(&sqlBuf, yylval.wdatum.idents);
int tok = yylex();
if (';' == tok)
{
appendStringInfo(&sqlBuf, " 1)");
$$ = make_callfunc_stmt(sqlBuf.data, @1, false, false, NULL, dno);
} else {
if('(' == tok) {
int tok1 = yylex();
if (tok1 == ')') {
int tok2 = yylex();
if (tok2 == ';') {
appendStringInfo(&sqlBuf, " 1)");
$$ = make_callfunc_stmt(sqlBuf.data, @1, false, false, NULL, dno);
} else {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
}
} else if (tok1 != ICONST && tok1 != T_WORD && tok1 != SCONST && tok1 != T_DATUM) {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
} else {
if (ICONST == tok1) {
appendStringInfo(&sqlBuf, " %d)", yylval.ival);
} else if (SCONST == tok1) {
appendStringInfo(&sqlBuf, " %s) ", yylval.str);
} else if (T_WORD == tok1) {
appendStringInfo(&sqlBuf, " %s)", yylval.word.ident);
} else {
char *datName = NameOfDatum(&yylval.wdatum);
appendStringInfo(&sqlBuf, " %s) ", datName);
pfree_ext(datName);
}
int tok2 = yylex();
if (tok2 != ')') {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
} else {
int tok3 = yylex();
if (tok3 != ';') {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
} else {
$$ = make_callfunc_stmt(sqlBuf.data, @1, false, false, NULL, dno);
}
}
}
} else {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
}
}
pfree_ext(sqlBuf.data);
}
| T_ARRAY_EXTEND
{
check_table_index(yylval.wdatum.datum, "trim");
int dno = yylval.wdatum.dno;
StringInfoData sqlBuf;
initStringInfo(&sqlBuf);
appendStringInfo(&sqlBuf, "array_extendnull(");
CastArrayNameToArrayFunc(&sqlBuf, yylval.wdatum.idents);
int tok = yylex();
if (';' == tok)
{
appendStringInfo(&sqlBuf, " 1)");
$$ = make_callfunc_stmt(sqlBuf.data, @1, false, false, NULL, dno);
} else {
if('(' == tok) {
int tok1 = yylex();
if (tok1 == ')') {
int tok2 = yylex();
if (tok2 == ';') {
appendStringInfo(&sqlBuf, " 1)");
$$ = make_callfunc_stmt(sqlBuf.data, @1, false, false, NULL, dno);
} else {
plpgsql_push_back_token(tok2);
$$ = NULL;
yyerror("syntax error");
}
} else if (tok1 != ICONST && tok1 != T_WORD && tok1 != SCONST && tok1 != T_DATUM) {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
} else {
if (ICONST == tok1) {
appendStringInfo(&sqlBuf, " %d) ", yylval.ival);
} else if (SCONST == tok1) {
appendStringInfo(&sqlBuf, " %s) ", yylval.str);
} else if (T_WORD == tok1) {
appendStringInfo(&sqlBuf, " %s) ", yylval.word.ident);
} else {
char *datName = NameOfDatum(&yylval.wdatum);
appendStringInfo(&sqlBuf, " %s) ", datName);
pfree_ext(datName);
}
int tok2 = yylex();
if (tok2 != ')') {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
} else {
int tok3 = yylex();
if (tok3 != ';') {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
} else {
$$ = make_callfunc_stmt(sqlBuf.data, @1, false, false, NULL, dno);
}
}
}
} else {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
}
}
pfree_ext(sqlBuf.data);
}
| T_ARRAY_DELETE
{
StringInfoData sqlBuf;
Oid indexType = get_table_index_type(yylval.wdatum.datum, NULL);
List* idents = yylval.wdatum.idents;
int dno = yylval.wdatum.dno;
initStringInfo(&sqlBuf);
int tok = yylex();
if (';' == tok)
{
if (indexType == VARCHAROID || indexType == INT4OID) {
appendStringInfo(&sqlBuf, "array_indexby_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents, false);
appendStringInfo(&sqlBuf, ")");
} else {
appendStringInfo(&sqlBuf, "array_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents, false);
appendStringInfo(&sqlBuf, ")");
}
$$ = make_callfunc_stmt(sqlBuf.data, @1, false, false, NULL, dno);
} else {
if('(' == tok) {
int tok1 = yylex();
if (tok1 == ')') {
int tok2 = yylex();
if (tok2 == ';') {
if (indexType == VARCHAROID || indexType == INT4OID) {
appendStringInfo(&sqlBuf, "array_indexby_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents, false);
appendStringInfo(&sqlBuf, ")");
} else {
appendStringInfo(&sqlBuf, "array_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents, false);
appendStringInfo(&sqlBuf, ")");
}
$$ = make_callfunc_stmt(sqlBuf.data, @1, false, false, NULL, dno);
} else {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
}
} else if (tok1 != ICONST && tok1 != T_WORD && tok1 != SCONST && tok1 != T_DATUM && tok1 != '-') {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
} else {
if (tok1 == '-') {
if (indexType == VARCHAROID) {
yyerror("syntax error");
}
int tok3 = yylex();
if (tok3 != ICONST && tok3 != T_WORD && tok3 != T_DATUM) {
yyerror("syntax error");
}
if (ICONST == tok3) {
if (indexType == INT4OID) {
appendStringInfo(&sqlBuf, "array_integer_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'-%d\')", yylval.ival);
} else {
appendStringInfo(&sqlBuf, "array_deleteidx(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "-%d)", yylval.ival);
}
} else if (T_WORD == tok3) {
if (indexType == INT4OID) {
appendStringInfo(&sqlBuf, "array_integer_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'-%s\')", yylval.word.ident);
} else {
appendStringInfo(&sqlBuf, "array_deleteidx(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "-%s)", yylval.word.ident);
}
} else {
char *datName = NameOfDatum(&yylval.wdatum);
if (indexType == INT4OID) {
appendStringInfo(&sqlBuf, "array_integer_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'-%s\')", datName);
} else {
appendStringInfo(&sqlBuf, "array_deleteidx(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "-%s)", datName);
}
pfree_ext(datName);
}
} else {
if (ICONST == tok1) {
if (indexType == VARCHAROID) {
appendStringInfo(&sqlBuf, "array_varchar_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'%d\')", yylval.ival);
} else if (indexType == INT4OID) {
appendStringInfo(&sqlBuf, "array_integer_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'%d\')", yylval.ival);
} else {
appendStringInfo(&sqlBuf, "array_deleteidx(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "%d)", yylval.ival);
}
} else if (SCONST == tok1) {
if (indexType == VARCHAROID) {
appendStringInfo(&sqlBuf, "array_varchar_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'%s\')", yylval.str);
} else if (indexType == INT4OID) {
appendStringInfo(&sqlBuf, "array_integer_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'%s\')", yylval.str);
} else {
appendStringInfo(&sqlBuf, "array_deleteidx(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "%s)", yylval.str);
}
} else if (T_WORD == tok1) {
if (indexType == VARCHAROID) {
appendStringInfo(&sqlBuf, "array_varchar_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'%s\')", yylval.word.ident);
} else if (indexType == INT4OID) {
appendStringInfo(&sqlBuf, "array_integer_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'%s\')", yylval.word.ident);
} else {
appendStringInfo(&sqlBuf, "array_deleteidx(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "%s)", yylval.word.ident);
}
} else {
char *datName = NameOfDatum(&yylval.wdatum);
if (indexType == VARCHAROID) {
appendStringInfo(&sqlBuf, "array_varchar_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'%s\')", datName);
} else if (indexType == INT4OID) {
appendStringInfo(&sqlBuf, "array_integer_delete(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "\'%s\')", datName);
} else {
appendStringInfo(&sqlBuf, "array_deleteidx(");
CastArrayNameToArrayFunc(&sqlBuf, idents);
appendStringInfo(&sqlBuf, "%s)", datName);
}
pfree_ext(datName);
}
}
int tok2 = yylex();
if (tok2 != ')') {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
} else {
int tok3 = yylex();
if (tok3 != ';') {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
} else {
$$ = make_callfunc_stmt(sqlBuf.data, @1, false, false, NULL, dno);
}
}
}
} else {
plpgsql_push_back_token(tok);
$$ = NULL;
yyerror("syntax error");
}
}
pfree_ext(sqlBuf.data);
}
;
stmt_dynexecute : K_EXECUTE
{
PLpgSQL_stmt_dynexecute *newp;
PLpgSQL_expr *expr;
int endtoken;
if((endtoken = yylex()) != K_IMMEDIATE)
{
plpgsql_push_back_token(endtoken);
}
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
"SELECT ",
true, true, true,
NULL, &endtoken);
newp = (PLpgSQL_stmt_dynexecute*)palloc0(sizeof(PLpgSQL_stmt_dynexecute));
newp->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->query = expr;
newp->into = false;
newp->strict = false;
newp->rec = NULL;
newp->row = NULL;
newp->params = NIL;
newp->out_row = NULL;
newp->isinouttype = false;
newp->ppd = NULL;
newp->isanonymousblock = true;
newp->sqlString = plpgsql_get_curline_query();
if (endtoken == K_BULK)
{
yyerror("syntax error");
}
/* If we found "INTO", collect the argument */
if (endtoken == K_INTO)
{
if (newp->into) /* multiple INTO */
yyerror("syntax error");
newp->into = true;
read_into_target(&newp->rec, &newp->row, &newp->strict, false);
endtoken = yylex();
}
/* If we found "USING", collect the argument */
if(endtoken == K_USING)
{
PLpgSQL_row *out_row;
if (newp->params) /* multiple USING */
yyerror("syntax error");
read_using_target(&(newp->params), &out_row );
if(out_row)
{
newp->into = true;
newp->strict = true;
newp->isinouttype = true;
}
endtoken = yylex();
if( out_row && newp->row )
{
const char* message = "target into is conflicted with using out (inout)";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("target into is conflicted with using out (inout)"),
errdetail("\"select clause\" can't has out parameters, can only use \"into\"")));
u_sess->plsql_cxt.have_error = true;
}
newp->out_row = newp->row ? newp->row:out_row;
}
if (endtoken != ';')
yyerror("syntax error");
$$ = (PLpgSQL_stmt *)newp;
}
;
stmt_open : K_OPEN cursor_variable
{
PLpgSQL_stmt_open *newp;
int tok;
int endtoken;
PLpgSQL_row *out_row = NULL;
newp = (PLpgSQL_stmt_open *)palloc0(sizeof(PLpgSQL_stmt_open));
newp->cmd_type = PLPGSQL_STMT_OPEN;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->curvar = $2;
newp->cursor_options = CURSOR_OPT_FAST_PLAN;
newp->sqlString = plpgsql_get_curline_query();
#ifndef ENABLE_MULTIPLE_NODES
if ((newp->curvar < 0 || newp->curvar >= u_sess->plsql_cxt.curr_compile_context->plpgsql_nDatums)
&& u_sess->attr.attr_common.plsql_show_all_error)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), errmsg("open cursor error"), parser_errposition(@2)));
}
#endif
PLpgSQL_var* cur_var = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[$2];
if (cur_var->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"))
{
newp->cursor_options |= CURSOR_OPT_NO_SCROLL;
tok = yylex();
}
}
else if (tok_is_keyword(tok, &yylval,
K_SCROLL, "scroll"))
{
newp->cursor_options |= CURSOR_OPT_SCROLL;
tok = yylex();
}
if (tok != K_FOR)
yyerror("syntax error, expected \"FOR\"");
tok = yylex();
if (tok == K_EXECUTE)
{
newp->dynquery = read_sql_stmt("select ");
}
else
{
plpgsql_push_back_token(tok);
if (tok == K_SELECT || tok == K_WITH)
{
newp->query = read_sql_stmt("");
}
else
{
newp->dynquery =
read_sql_expression2(K_USING, ';',
"USING or ;",
&endtoken);
/* If we found "USING", collect argument(s) */
if(K_USING == endtoken)
{
read_using_target(&(newp->params), &out_row);
if(NULL != out_row)
yyerror("syntax error");
if(NULL == newp->params)
yyerror("syntax error");
endtoken = plpgsql_yylex();
}
if(';' != endtoken )
yyerror("syntax error");
}
}
#ifndef ENABLE_MULTIPLE_NODES
if (newp->query != NULL && u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && ALLOW_PROCEDURE_COMPILE_CHECK) {
(void)getCursorTupleDesc(newp->query, false, true);
}
else if (newp->dynquery != NULL && u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && ALLOW_PROCEDURE_COMPILE_CHECK)
{
(void)getCursorTupleDesc(newp->dynquery, false, true);
}
#endif
}
else
{
/* predefined cursor query, so read args */
newp->argquery = read_cursor_args(cur_var, ';', ";");
}
$$ = (PLpgSQL_stmt *)newp;
}
;
stmt_fetch : K_FETCH opt_fetch_direction cursor_variable K_INTO
{
PLpgSQL_stmt_fetch *fetch = $2;
PLpgSQL_rec *rec;
PLpgSQL_row *row;
/* We have already parsed everything through the INTO keyword */
read_into_target(&rec, &row, NULL, false);
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) {
const char* message = "FETCH statement cannot return multiple rows";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("FETCH statement cannot return multiple rows"),
parser_errposition(@1)));
}
fetch->lineno = plpgsql_location_to_lineno(@1);
fetch->rec = rec;
fetch->row = row;
fetch->curvar = $3;
fetch->is_move = false;
fetch->bulk_collect = false;
fetch->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *)fetch;
}
| K_FETCH opt_fetch_direction cursor_variable K_BULK K_COLLECT K_INTO fetch_into_target fetch_limit_expr
{
PLpgSQL_stmt_fetch *fetch = $2;
if (fetch->has_direction) {
yyerror("unexpected fetch direction statement");
}
/* only A_FORMAT can use bulk collect into */
if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
yyerror("syntax error");
}
PLpgSQL_datum *datum = $7;
fetch->rec = NULL;
fetch->row = NULL;
if (datum->dtype == PLPGSQL_DTYPE_REC) {
fetch->rec = (PLpgSQL_rec *)datum;
} else {
fetch->row = (PLpgSQL_row *)datum;
check_bulk_into_type((PLpgSQL_row*)datum);
}
fetch->lineno = plpgsql_location_to_lineno(@1);
fetch->curvar = $3;
fetch->how_many = FETCH_ALL;
fetch->is_move = false;
fetch->expr = $8;
fetch->returns_multiple_rows = true;
fetch->bulk_collect = true;
fetch->sqlString = plpgsql_get_curline_query();
$$ = (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;
fetch->is_move = true;
fetch->bulk_collect = false;
fetch->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *)fetch;
}
;
opt_fetch_direction :
{
$$ = read_fetch_direction();
}
;
fetch_limit_expr : K_LIMIT
{
$$ = read_sql_expression(';', ";");
}
| ';'
{
$$ = NULL;
}
;
fetch_into_target :
{
PLpgSQL_datum *datum = NULL;
PLpgSQL_rec *rec;
PLpgSQL_row *row;
read_into_target(&rec, &row, NULL, true);
if (rec != NULL) {
datum = (PLpgSQL_datum *)rec;
} else {
datum = (PLpgSQL_datum *)row;
}
$$ = datum;
}
;
stmt_close : K_CLOSE cursor_variable ';'
{
PLpgSQL_stmt_close *newp;
newp = (PLpgSQL_stmt_close *)palloc(sizeof(PLpgSQL_stmt_close));
newp->cmd_type = PLPGSQL_STMT_CLOSE;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->curvar = $2;
newp->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *)newp;
}
;
stmt_null : K_NULL ';'
{
/* We do building a node for NULL for GOTO */
PLpgSQL_stmt_null *newp;
newp = (PLpgSQL_stmt_null *)palloc(sizeof(PLpgSQL_stmt_null));
newp->cmd_type = PLPGSQL_STMT_NULL;
newp->lineno = plpgsql_location_to_lineno(@1);
newp->sqlString = plpgsql_get_curline_query();
$$ = (PLpgSQL_stmt *)newp;
}
;
stmt_commit : opt_block_label K_COMMIT ';'
{
PLpgSQL_stmt_commit *newp;
newp = (PLpgSQL_stmt_commit *)palloc(sizeof(PLpgSQL_stmt_commit));
newp->cmd_type = PLPGSQL_STMT_COMMIT;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->sqlString = plpgsql_get_curline_query();
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)newp;
record_stmt_label($1, (PLpgSQL_stmt *)newp);
}
;
savepoint_name : any_identifier
{
$$ = $1;
}
;
opt_savepoint_name :
K_SAVEPOINT savepoint_name
{
$$ = $2;
}
| savepoint_name
{
$$ = $1;
}
;
opt_rollback_to :
{
$$ = NULL;
}
| K_TO opt_savepoint_name
{
$$ = $2;
}
;
stmt_rollback : opt_block_label K_ROLLBACK opt_rollback_to ';'
{
if ($3 == NULL) {
PLpgSQL_stmt_rollback *newp;
newp = (PLpgSQL_stmt_rollback *) palloc(sizeof(PLpgSQL_stmt_rollback));
newp->cmd_type = PLPGSQL_STMT_ROLLBACK;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->sqlString = plpgsql_get_curline_query();
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)newp;
record_stmt_label($1, (PLpgSQL_stmt *)newp);
} else {
PLpgSQL_stmt_savepoint *newp;
newp = (PLpgSQL_stmt_savepoint *) palloc(sizeof(PLpgSQL_stmt_savepoint));
newp->cmd_type = PLPGSQL_STMT_SAVEPOINT;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->sqlString = plpgsql_get_curline_query();
newp->opType = PLPGSQL_SAVEPOINT_ROLLBACKTO;
newp->spName = $3;
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)newp;
record_stmt_label($1, (PLpgSQL_stmt *)newp);
}
}
;
stmt_savepoint : opt_block_label K_SAVEPOINT savepoint_name ';'
{
PLpgSQL_stmt_savepoint *newp;
newp = (PLpgSQL_stmt_savepoint *) palloc(sizeof(PLpgSQL_stmt_savepoint));
newp->cmd_type = PLPGSQL_STMT_SAVEPOINT;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->sqlString = plpgsql_get_curline_query();
newp->opType = PLPGSQL_SAVEPOINT_CREATE;
newp->spName = $3;
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)newp;
record_stmt_label($1, (PLpgSQL_stmt *)newp);
}
| opt_block_label K_RELEASE opt_savepoint_name ';'
{
PLpgSQL_stmt_savepoint *newp;
newp = (PLpgSQL_stmt_savepoint *) palloc(sizeof(PLpgSQL_stmt_savepoint));
newp->cmd_type = PLPGSQL_STMT_SAVEPOINT;
newp->lineno = plpgsql_location_to_lineno(@2);
newp->sqlString = plpgsql_get_curline_query();
newp->opType = PLPGSQL_SAVEPOINT_RELEASE;
newp->spName = $3;
plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)newp;
record_stmt_label($1, (PLpgSQL_stmt *)newp);
}
;
cursor_variable : T_DATUM
{
if ($1.datum->dtype != PLPGSQL_DTYPE_VAR) {
const char* message = "cursor variable must be a simple variable";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cursor variable must be a simple variable"),
parser_errposition(@1)));
}
if (((PLpgSQL_var *) $1.datum)->datatype->typoid != REFCURSOROID) {
const char* message = "variable must be of type cursor or refcursor";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("variable \"%s\" must be of type cursor or refcursor",
((PLpgSQL_var *) $1.datum)->refname),
parser_errposition(@1)));
}
if (((PLpgSQL_var *) $1.datum)->ispkg) {
if ((u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile != NULL &&
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->is_autonomous) ||
u_sess->is_autonomous_session) {
const char* message =
"package cursor referenced in autonomous procedure is not supported yet";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cursor referenced by \"%s\" in autonomous procedure is not supported yet",
$1.ident),
parser_errposition(@1)));
}
}
$$ = $1.dno;
}
| T_PACKAGE_VARIABLE
{
if ($1.datum->dtype != PLPGSQL_DTYPE_VAR) {
const char* message = "cursor variable must be a simple variable";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cursor variable must be a simple variable"),
parser_errposition(@1)));
}
if (((PLpgSQL_var *) $1.datum)->datatype->typoid != REFCURSOROID) {
const char* message = "variable must be of type cursor or refcursor";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("variable \"%s\" must be of type cursor or refcursor",
((PLpgSQL_var *) $1.datum)->refname),
parser_errposition(@1)));
}
if (list_length($1.idents) == 3) {
const char* message =
"cursor referenced in schema.package.cursor format is not supported yet";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cursor referenced by \"%s\" is not supported yet",
$1.ident),
parser_errposition(@1)));
}
if ((u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile != NULL &&
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->is_autonomous) ||
u_sess->is_autonomous_session) {
const char* message =
"package cursor referenced in autonomous procedure is not supported yet";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cursor referenced by \"%s\" in autonomous procedure is not supported yet",
$1.ident),
parser_errposition(@1)));
}
$$ = $1.dno;
}
| 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.
*/
PLpgSQL_exception_block *newp = (PLpgSQL_exception_block *)palloc(sizeof(PLpgSQL_exception_block));
$<exception_block>$ = newp;
}
proc_exceptions
{
PLpgSQL_exception_block *newp = $<exception_block>2;
newp->exc_list = $3;
$$ = newp;
}
;
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 *newp;
newp = (PLpgSQL_exception *)palloc0(sizeof(PLpgSQL_exception));
newp->lineno = plpgsql_location_to_lineno(@1);
newp->conditions = $2;
newp->action = $4;
$$ = newp;
}
;
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)
{
if (PLPGSQL_DTYPE_ROW == yylval.wdatum.datum->dtype)
{
PLpgSQL_condition *newp = NULL;
PLpgSQL_row* row = (PLpgSQL_row*)yylval.wdatum.datum;
if (!row->ispkg) {
row = ( PLpgSQL_row* ) u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[yylval.wdatum.dno];
} else {
PLpgSQL_package* pkg = row->pkg;
row = ( PLpgSQL_row* )pkg->datums[row->dno];
}
TupleDesc rowtupdesc = row ? row->rowtupdesc : NULL;
if(rowtupdesc &&
0 == strcmp(format_type_be(rowtupdesc->tdtypeid), "exception"))
{
newp = (PLpgSQL_condition *)palloc(sizeof(PLpgSQL_condition));
newp->sqlerrstate = row->customErrorCode;
newp->condname = pstrdup(row->refname);
newp->next = NULL;
}
if(NULL == newp) {
const char* message = "unrecognized exception condition ";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unrecognized exception condition \"%s\"",
row? row->refname : "??")));
}
$$ = newp;
}
else {
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
$$ = plpgsql_parse_err_condition($1);
}
}
else
{
PLpgSQL_condition *newp;
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");
newp = (PLpgSQL_condition *)palloc(sizeof(PLpgSQL_condition));
newp->sqlerrstate =
MAKE_SQLSTATE(sqlstatestr[0],
sqlstatestr[1],
sqlstatestr[2],
sqlstatestr[3],
sqlstatestr[4]);
newp->condname = sqlstatestr;
newp->next = NULL;
$$ = newp;
}
}
;
expr_until_semi :
{
/*
* support invoking function with out
* argument in a := expression
*/
PLpgSQL_stmt *stmt = NULL;
int tok = -1;
char *name = NULL;
bool isCallFunc = false;
PLpgSQL_expr* expr = NULL;
List* funcNameList = NULL;
if (plpgsql_is_token_match2(T_WORD, '(') ||
plpgsql_is_token_match2(T_CWORD,'('))
{
tok = yylex();
if (T_WORD == tok) {
name = yylval.word.ident;
isCallFunc = is_function(name, true, false);
} else {
checkFuncName(yylval.cword.idents);
funcNameList = yylval.cword.idents;
name = NameListToString(yylval.cword.idents);
isCallFunc = is_function(name, true, false, yylval.cword.idents);
}
}
if (isCallFunc)
{
stmt = make_callfunc_stmt(name, yylloc, true, false, funcNameList, -1, isCallFunc);
if (PLPGSQL_STMT_EXECSQL == stmt->cmd_type)
expr = ((PLpgSQL_stmt_execsql*)stmt)->sqlstmt;
else if (PLPGSQL_STMT_PERFORM == stmt->cmd_type)
expr = ((PLpgSQL_stmt_perform*)stmt)->expr;
expr->is_funccall = true;
$$ = expr;
}
else
{
if (name != NULL) {
plpgsql_push_back_token(tok);
name = NULL;
}
if (!plpgsql_is_token_match2(T_CWORD,'('))
{
if (plpgsql_is_token_match(T_CWORD)) {
List* wholeName = yylval.cword.idents;
plpgsql_pkg_add_unknown_var_to_namespace(wholeName);
}
}
$$ = read_sql_expression(';', ";");
}
}
;
expr_until_rightbracket :
{ $$ = read_sql_expression(']', "]"); }
;
expr_until_parenthesis :
{ $$ = 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);
$$ = NULL;
}
| LESS_LESS any_identifier GREATER_GREATER
{
plpgsql_ns_push($2);
$$ = $2;
}
;
opt_label :
{
$$ = NULL;
}
| any_identifier
{
$$ = $1;
}
;
opt_exitcond : ';'
{ $$ = NULL; }
| K_WHEN expr_until_semi
{ $$ = $2; }
;
/*
* need both options because scanner will have tried to resolve as variable
*/
any_identifier : T_WORD
{
$$ = $1.ident;
}
| T_DATUM
{
if ($1.ident == NULL && $1.idents == NULL) {
yyerror("syntax error");
} else if ($1.ident != NULL) {
$$ = $1.ident;
} else {
char* names = NameListToString($1.idents);
$$ = names;
}
}
;
unreserved_keyword_func :
K_ABSOLUTE
| K_ALIAS
| K_BACKWARD
| K_CONSTANT
| K_CURRENT
| K_DEBUG
| K_DETAIL
| K_DUMP
| K_ERRCODE
| K_ERROR
| K_EXCEPTIONS
| K_FIRST
| K_FORWARD
| K_HINT
| K_INDEX
| K_INFO
| K_LAST
| K_LOG
| K_MESSAGE
| K_MESSAGE_TEXT
| K_MULTISET
| K_NEXT
| K_NO
| K_NOTICE
| K_OPTION
| K_PACKAGE
| K_INSTANTIATION
| K_PG_EXCEPTION_CONTEXT
| K_PG_EXCEPTION_DETAIL
| K_PG_EXCEPTION_HINT
| K_QUERY
| K_RECORD
| K_RELATIVE
| K_RESULT_OID
| K_RETURNED_SQLSTATE
| K_REVERSE
| K_ROW_COUNT
| K_SCROLL
| K_SLICE
| K_STACKED
| K_SYS_REFCURSOR
| K_USE_COLUMN
| K_USE_VARIABLE
| K_VARIABLE_CONFLICT
| K_VARRAY
| K_WARNING
;
unreserved_keyword :
K_ABSOLUTE
| K_ALIAS
| K_ALTER
| K_ARRAY
| K_BACKWARD
| K_COMMIT
| K_CONSTANT
| K_CONTINUE
| K_CURRENT
| K_DEBUG
| K_DETAIL
| K_DISTINCT
| K_DUMP
| K_ERRCODE
| K_ERROR
| K_EXCEPT
| K_EXCEPTIONS
| K_FIRST
| K_FORWARD
| K_HINT
| K_INDEX
| K_INFO
| K_INTERSECT
| K_IS
| K_LAST
| K_LOG
| K_MERGE
| K_MESSAGE
| K_MESSAGE_TEXT
| K_MULTISET
| K_NEXT
| K_NO
| K_NOTICE
| K_OPTION
| K_PACKAGE
| K_INSTANTIATION
| K_PG_EXCEPTION_CONTEXT
| K_PG_EXCEPTION_DETAIL
| K_PG_EXCEPTION_HINT
| K_PRIOR
| K_QUERY
| K_RECORD
| K_RELATIVE
| K_RESULT_OID
| K_RETURNED_SQLSTATE
| K_REVERSE
| K_ROLLBACK
| K_ROW_COUNT
| K_ROWTYPE
| K_SAVE
| K_SCROLL
| K_SLICE
| K_SQLSTATE
| K_STACKED
| K_SYS_REFCURSOR
| K_TABLE
| K_UNION
| K_USE_COLUMN
| K_USE_VARIABLE
| K_VARIABLE_CONFLICT
| K_VARRAY
| K_WARNING
| K_WITH
;
%%
#define MAX_EXPR_PARAMS 1024
typedef struct {
YYSTYPE lval; /* semantic information */
YYLTYPE lloc; /* offset in scanbuf */
int tok; /* token */
int leng;
} TokenData;
static DList* push_token_stack(int tok, DList* tokenstack);
static TokenData* build_token_data(int token);
/*
* 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)
{
const char* message = "it is not a known variable ";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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)
{
const char* message = "it is not a known variable ";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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");
}
/*
* Brief : handle the C-style function IN arguments.
* Description :
* Notes :
*/
static void
yylex_inparam(StringInfoData* func_inparam,
int *nparams,
int * tok,
int *tableof_func_dno,
int *tableof_var_dno)
{
PLpgSQL_expr * expr = NULL;
if (nparams != NULL) {
if (*nparams) {
appendStringInfoString(func_inparam, ",");
}
/*
* handle the problem that the function
* arguments can only be variable. the argment validsql is set FALSE to
* ignore sql expression check to the "$n" type of arguments.
*/
expr = read_sql_construct(',', ')', 0, ",|)", "", true, false, false, NULL, tok);
if (*nparams >= MAX_EXPR_PARAMS) {
const char* message = "too many variables specified in SQL statement ";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many variables specified in SQL statement ,more than %d", MAX_EXPR_PARAMS)));
}
(*nparams)++;
} else {
expr = read_sql_construct(',', ')', 0, ",|)", "", true, false, false, NULL, tok);
}
if (*tableof_func_dno != -1 && *tableof_func_dno != expr->tableof_func_dno) {
yyerror("do not support more than 2 table of index by variables call functions in function");
} else {
*tableof_func_dno = expr->tableof_func_dno;
}
*tableof_var_dno = expr->tableof_var_dno;
/*
* handle the problem that the function
* arguments can only be variable. After revising, the arguments can be any
* expression.
*/
appendStringInfoString(func_inparam, expr->query);
}
/*
* Brief : handle the C-style function OUT arguments.
* Description :
* Notes :
*/
static int
yylex_outparam(char** fieldnames,
int *varnos,
int nfields,
PLpgSQL_row **row,
PLpgSQL_rec **rec,
int *token,
bool is_push_back,
bool overload)
{
*token = yylex();
int loc = yylloc;
if (T_DATUM == *token)
{
if (PLPGSQL_DTYPE_ROW == yylval.wdatum.datum->dtype)
{
check_assignable(yylval.wdatum.datum, yylloc);
fieldnames[nfields] = pstrdup(NameOfDatum(&yylval.wdatum));
int varno = yylval.wdatum.dno;
TokenData* temptokendata = build_token_data(*token);
int temtoken = yylex();
plpgsql_push_back_token(temtoken);
yylloc = temptokendata->lloc;
yylval = temptokendata->lval;
u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng = temptokendata->leng;
/* if is a.b.c.d? */
if ('.' == temtoken) {
varnos[nfields] = read_assignlist(is_push_back, token);
} else {
varnos[nfields] = varno;
*row = (PLpgSQL_row *)yylval.wdatum.datum;
}
if (is_push_back) {
yylloc = temptokendata->lloc;
yylval = temptokendata->lval;
u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng = temptokendata->leng;
}
pfree_ext(temptokendata);
}
else if (PLPGSQL_DTYPE_RECORD == yylval.wdatum.datum->dtype)
{
check_assignable(yylval.wdatum.datum, yylloc);
fieldnames[nfields] = pstrdup(NameOfDatum(&yylval.wdatum));
varnos[nfields] = yylval.wdatum.dno;
*row = (PLpgSQL_row *)yylval.wdatum.datum;
}
else if (PLPGSQL_DTYPE_REC == yylval.wdatum.datum->dtype)
{
check_assignable(yylval.wdatum.datum, yylloc);
fieldnames[nfields] = pstrdup(NameOfDatum(&yylval.wdatum));
varnos[nfields] = yylval.wdatum.dno;
*rec = (PLpgSQL_rec *)yylval.wdatum.datum;
}
else if (PLPGSQL_DTYPE_VAR == yylval.wdatum.datum->dtype)
{
check_assignable(yylval.wdatum.datum, yylloc);
fieldnames[nfields] = pstrdup(NameOfDatum(&yylval.wdatum));
varnos[nfields] = yylval.wdatum.dno;
}
/* when out param is rec.a which res is not define, we pass the check */
else if (PLPGSQL_DTYPE_RECFIELD == yylval.wdatum.datum->dtype)
{
check_assignable(yylval.wdatum.datum, yylloc);
fieldnames[nfields] = pstrdup(NameOfDatum(&yylval.wdatum));
varnos[nfields] = yylval.wdatum.datum->dno;
}
else {
const char* message = "it is not a scalar variable ";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
NameOfDatum(&yylval.wdatum))));
}
}
else if (T_VARRAY_VAR == *token || T_TABLE_VAR == *token)
{
/* Temporary processing array value for out param */
check_assignable(yylval.wdatum.datum, yylloc);
fieldnames[nfields] = CopyNameOfDatum(&yylval.wdatum);
varnos[nfields] = read_assignlist(is_push_back, token);
}
else if (T_PACKAGE_VARIABLE == *token)
{
if (PLPGSQL_DTYPE_ROW == yylval.wdatum.datum->dtype)
{
check_assignable(yylval.wdatum.datum, yylloc);
fieldnames[nfields] = pstrdup(NameOfDatum(&yylval.wdatum));
int varno = yylval.wdatum.dno;
TokenData* temptokendata = build_token_data(*token);
int temtoken = yylex();
plpgsql_push_back_token(temtoken);
yylloc = temptokendata->lloc;
yylval = temptokendata->lval;
u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng = temptokendata->leng;
/* if is a.b.c.d? */
if ('.' == temtoken) {
varnos[nfields] = read_assignlist(is_push_back, token);
} else {
varnos[nfields] = varno;
*row = (PLpgSQL_row *)yylval.wdatum.datum;
}
if (is_push_back) {
yylloc = temptokendata->lloc;
yylval = temptokendata->lval;
u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng = temptokendata->leng;
}
pfree_ext(temptokendata);
}
else if (PLPGSQL_DTYPE_RECORD == yylval.wdatum.datum->dtype)
{
check_assignable(yylval.wdatum.datum, yylloc);
fieldnames[nfields] = pstrdup(NameOfDatum(&yylval.wdatum));
varnos[nfields] = yylval.wdatum.dno;
*row = (PLpgSQL_row *)yylval.wdatum.datum;
}
else if (PLPGSQL_DTYPE_VAR == yylval.wdatum.datum->dtype)
{
check_assignable(yylval.wdatum.datum, yylloc);
int type_flag = -1;
get_datum_tok_type(yylval.wdatum.datum, &type_flag);
if (type_flag == -1) {
fieldnames[nfields] = pstrdup(NameOfDatum(&yylval.wdatum));
varnos[nfields] = yylval.wdatum.dno;
} else {
/* Temporary processing array value for out param */
fieldnames[nfields] = CopyNameOfDatum(&yylval.wdatum);
varnos[nfields] = read_assignlist(is_push_back, token);
}
}
else {
const char* message = "it is not a scalar variable ";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
NameOfDatum(&yylval.wdatum))));
}
}
else if (overload)
{
fieldnames[nfields] = NULL;
varnos[nfields] = -1;
}
return loc;
}
static int read_assignlist(bool is_push_back, int* token)
{
int varno = yylval.wdatum.dno;
TokenData* temptokendata = build_token_data(*token);
DList* tokenstack = NULL;
List* assignlist = NULL;
for (;;) {
int temtoken = yylex();
if ('(' != temtoken && '[' != temtoken && '.' != temtoken) {
plpgsql_push_back_token(temtoken);
break;
}
if (is_push_back) {
TokenData* temptokendata1 = build_token_data(temtoken);
tokenstack = dlappend(tokenstack, temptokendata1);
}
PLpgSQL_expr* expr = NULL;
char* attrname = NULL;
if ('(' == temtoken) {
expr = read_sql_construct6(')', 0, 0, 0, 0, 0, ")", "SELECT ",
true, true, true, NULL, NULL, tokenstack, true);
} else if ('[' == temtoken) {
expr = read_sql_construct6(']', 0, 0, 0, 0, 0, "]", "SELECT ",
true, true, true, NULL, NULL, tokenstack, true);
} else {
int attrtoken = plpgsql_yylex_single();
attrname = get_attrname(attrtoken);
if (tokenstack != NULL) {
TokenData* temptokendata1 = build_token_data(attrtoken);
tokenstack = dlappend(tokenstack, temptokendata1);
}
}
MemoryContext oldCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_cxt);
if (expr != NULL) {
A_Indices *ai = makeNode(A_Indices);
ai->lidx = NULL;
ai->uidx = (Node*)expr;
assignlist = lappend(assignlist, (Node*)ai);
} else {
Value* v = makeString(pstrdup(attrname));
assignlist = lappend(assignlist, (Node*)v);
}
MemoryContextSwitchTo(oldCxt);
}
if (assignlist != NULL) {
MemoryContext oldCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_cxt);
PLpgSQL_assignlist *newp = NULL;
newp = (PLpgSQL_assignlist*)palloc0(sizeof(PLpgSQL_assignlist));
newp->dtype = PLPGSQL_DTYPE_ASSIGNLIST;
newp->assignlist = assignlist;
newp->targetno = varno;
varno = plpgsql_adddatum((PLpgSQL_datum*)newp);
MemoryContextSwitchTo(oldCxt);
}
if (is_push_back) {
if (tokenstack != NULL) {
push_back_token_stack(tokenstack);
}
/* need restore yylloc or yylval if push back twice */
yylloc = temptokendata->lloc;
yylval = temptokendata->lval;
u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng = temptokendata->leng;
}
pfree_ext(temptokendata);
return varno;
}
/* push token in tokenstack */
static DList* push_token_stack(int tok, DList* tokenstack)
{
TokenData* temptokendata = NULL;
if (tokenstack != NULL) {
temptokendata = build_token_data(tok);
tokenstack = dlappend(tokenstack, temptokendata);
}
return tokenstack;
}
/* build a TokenData for input token */
static TokenData* build_token_data(int token)
{
TokenData* temptokendata = (TokenData*)palloc0(sizeof(TokenData));
temptokendata->lloc = yylloc;
temptokendata->lval = yylval;
temptokendata->tok = token;
temptokendata->leng = u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng;
return temptokendata;
}
/* push back token stored in tokenstack */
static int push_back_token_stack(DList* tokenstack)
{
int tok = 0;
if(tokenstack->length > MAX_PUSHBACKS) {
dlist_free(tokenstack, true);
yyerror("array with subscript words too much for overloaded function.");
} else {
DListCell* dlc = NULL;
for (dlc = dlist_tail_cell(tokenstack); dlc != NULL; dlc = lprev(dlc)) {
/* plpgsql_push_back_token function only push yylloc and yylval,
* so assign the token value to them.
*/
TokenData* n = (TokenData*)lfirst(dlc);
tok = n->tok;
yylloc = n->lloc;
yylval = n->lval;
u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng = n->leng;
plpgsql_push_back_token(tok);
}
dlist_free(tokenstack, true);
}
return tok;
}
/*
* Brief: passe the called function name and return it into output.
* Description:
* in s: original idents
* inout output: splited idents.
* in numidnets: max ident number.
* returns: void
* Notes : No need to consider double quoted ident because it has been handled in lex.
* Ident here has been downcase if no double quoted, and been without quote if it had double quote before.
*/
void
plpgsql_parser_funcname(const char *s, char **output, int numidents)
{
int ident_num = 0;
char *outer_ptr = NULL;
char *p = NULL;
char delimiter[] = ".";
errno_t ret;
char *str = (char *)palloc0(sizeof(char) * (strlen(s) + 1));
for (int i = 0; i < numidents; i++)
output[i] = (char *)palloc0(sizeof(char) * NAMEDATALEN * 2);
ret = strcpy_s(str, sizeof(char) * (strlen(s) + 1), s);
securec_check(ret, "\0", "\0");
p = strtok_s(str, delimiter, &outer_ptr);
while (p != NULL)
{
ret = strcpy_s(output[ident_num++], sizeof(char) * NAMEDATALEN * 2, p);
securec_check(ret, "\0", "\0");
p = strtok_s(NULL, delimiter, &outer_ptr);
}
pfree_ext(str);
}
static int get_func_out_arg_num(char* p_argmodes, int all_arg)
{
if (all_arg == 0 || p_argmodes == NULL) {
return 0;
}
int out_arg_num = 0;
for (int i = 0; i < all_arg; i++) {
if (p_argmodes[i] == 'o' || p_argmodes[i] == 'b') {
out_arg_num++;
}
}
return out_arg_num;
}
/*
* Brief : handle A-style function call.
* Description : handle A-style function call. First read the function
* name, then read the arguments. At last assembing the function
* name and arguments into the postgresql-style function call.
* in sqlstart: the sql stmt string to be handle.
* in location: the location number to erreport.
* in is_assign: is an assign stmt or not.
* returns: the parsed stmt.
* Notes :
*/
static PLpgSQL_stmt *
make_callfunc_stmt(const char *sqlstart, int location, bool is_assign, bool eaten_first_token, List* funcNameList, int arrayFuncDno, bool isCallFunc)
{
int nparams = 0;
int nfields = 0;
int narg = 0;
int inArgNum = 0;
int i= 0;
int tok = 0;
Oid *p_argtypes = NULL;
char *cp[3];
char **p_argnames = NULL;
char *p_argmodes = NULL;
/* pos_outer is the postion got by matching the name. */
int pos_outer = 0;
/* pos_inner is the position got by its real postion in function invoke */
int pos_inner = -1;
int varnos[FUNC_MAX_ARGS] = {-1};
bool namedarg[FUNC_MAX_ARGS];
char* namedargnamses[FUNC_MAX_ARGS];
char *fieldnames[FUNC_MAX_ARGS];
bool outParamInvalid = false;
bool is_plpgsql_func_with_outparam = false;
List *funcname = NIL;
PLpgSQL_row *row = NULL;
PLpgSQL_rec *rec = NULL;
PLpgSQL_expr* expr = NULL;
HeapTuple proctup = NULL;
Form_pg_proc procStruct;
FuncCandidateList clist = NULL;
StringInfoData func_inparas;
bool noargs = FALSE;
int ndefaultargs = 0;
StringInfoData argname;
int j = 0;
int placeholders = 0;
char *quoted_sqlstart = NULL;
bool is_have_tableof_index_func = false;
MemoryContext oldCxt = NULL;
bool multi_func = false;
const char *varray_delete = "array_delete(\"";
const char *varray_indexby_delete = "array_indexby_delete(\"";
const char *varray_deleteidx = "array_deleteidx(\"";
const char *varray_deletevarchar = "array_varchar_delete(\"";
const char *varray_deleteinteger = "array_integer_delete(\"";
const char *varray_extend= "array_extendnull(\"";
const char *varray_trim = "array_trim(\"";
/*get the function's name*/
cp[0] = NULL;
cp[1] = NULL;
cp[2] = NULL;
if (sqlstart != NULL && (strncmp(sqlstart, varray_delete, strlen(varray_delete)) == 0
|| strncmp(sqlstart, varray_indexby_delete, strlen(varray_indexby_delete)) == 0
|| strncmp(sqlstart, varray_deleteidx, strlen(varray_deleteidx)) == 0
|| strncmp(sqlstart, varray_extend, strlen(varray_extend)) == 0
|| strncmp(sqlstart, varray_trim, strlen(varray_trim)) == 0
|| strncmp(sqlstart, varray_deletevarchar, strlen(varray_deletevarchar)) == 0
|| strncmp(sqlstart, varray_deleteinteger, strlen(varray_deleteinteger)) == 0)) {
unsigned int chIdx = 0;
if (strncmp(sqlstart, varray_delete, strlen(varray_delete)) == 0) {
chIdx = strlen(varray_delete);
} else if (strncmp(sqlstart, varray_indexby_delete, strlen(varray_indexby_delete)) == 0) {
chIdx = strlen(varray_indexby_delete);
is_have_tableof_index_func = true;
} else if (strncmp(sqlstart, varray_deleteidx, strlen(varray_deleteidx)) == 0) {
chIdx = strlen(varray_deleteidx);
} else if (strncmp(sqlstart, varray_deletevarchar, strlen(varray_deletevarchar)) == 0) {
chIdx = strlen(varray_deletevarchar);
is_have_tableof_index_func = true;
} else if (strncmp(sqlstart, varray_deleteinteger, strlen(varray_deleteinteger)) == 0) {
chIdx = strlen(varray_deleteinteger);
is_have_tableof_index_func = true;
} else if (strncmp(sqlstart, varray_trim, strlen(varray_trim)) == 0) {
chIdx = strlen(varray_trim);
} else {
chIdx = strlen(varray_extend);
}
initStringInfo(&func_inparas);
appendStringInfoString(&func_inparas, "SELECT ");
appendStringInfoString(&func_inparas, sqlstart);
if (arrayFuncDno == -1) {
return NULL;
}
expr = (PLpgSQL_expr *)palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(func_inparas.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->ns = plpgsql_ns_top();
expr->idx = (uint32)-1;
expr->out_param_dno = -1;
expr->is_have_tableof_index_func = is_have_tableof_index_func;
PLpgSQL_stmt_assign *perform = (PLpgSQL_stmt_assign*)palloc0(sizeof(PLpgSQL_stmt_assign));
perform->cmd_type = PLPGSQL_STMT_ASSIGN;;
perform->lineno = plpgsql_location_to_lineno(location);
perform->varno = arrayFuncDno;
perform->expr = expr;
perform->sqlString = pstrdup(func_inparas.data);
pfree_ext(func_inparas.data);
return (PLpgSQL_stmt *)perform;
}
/* the function make_callfunc_stmt is only to assemble a sql statement, so the context is set to tmp context */
oldCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_tmp_cxt);
if (funcNameList == NULL) {
plpgsql_parser_funcname(sqlstart, cp, 3);
} else {
funcname= funcNameList;
}
if (funcNameList == NULL) {
if (cp[2] && cp[2][0] != '\0')
funcname = list_make3(makeString(cp[0]), makeString(cp[1]), makeString(cp[2]));
else if (cp[1] && cp[1][0] != '\0')
funcname = list_make2(makeString(cp[0]), makeString(cp[1]));
else
funcname = list_make1(makeString(cp[0]));
}
/* search the function */
clist = FuncnameGetCandidates(funcname, -1, NIL, false, false, false);
if (!clist)
{
const char* message = "function not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function \"%s\" doesn't exist ", sqlstart)));
return NULL;
}
if (clist->next)
{
multi_func = true;
if (IsPackageFunction(funcname) == false)
{
const char* message = "function is not exclusive";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DUPLICATE_FUNCTION),
errmsg("function \"%s\" isn't exclusive ", sqlstart)));
}
}
if (multi_func == false)
{
proctup = SearchSysCache(PROCOID,
ObjectIdGetDatum(clist->oid),
0, 0, 0);
/*
* function may be deleted after clist be searched.
*/
if (!HeapTupleIsValid(proctup))
{
const char* message = "function does not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function \"%s\" doesn't exist ", sqlstart)));
return NULL;
}
/* get the all args informations, only "in" parameters if p_argmodes is null */
narg = get_func_arg_info(proctup,&p_argtypes, &p_argnames, &p_argmodes);
if (p_argmodes) {
for (i = 0; i < narg; i++) {
if (p_argmodes[i] == 'i' || p_argmodes[i] == 'b') {
inArgNum++;
}
}
} else {
inArgNum = narg;
}
procStruct = (Form_pg_proc) GETSTRUCT(proctup);
ndefaultargs = procStruct->pronargdefaults;
ReleaseSysCache(proctup);
}
initStringInfo(&func_inparas);
tok = yylex();
/*
* check the format for the function without parameters
* if eaten_first_token is true,first token is '(' ,so if next token is ')',it means
* no args
*/
if (eaten_first_token==true) {
if (tok == ')')
noargs = TRUE;
} else {
if ((tok = yylex()) == ')')
noargs = TRUE;
}
plpgsql_push_back_token(tok);
if (isCallFunc && is_function_with_plpgsql_language_and_outparam(clist->oid)) {
is_assign = false;
is_plpgsql_func_with_outparam = true;
}
/* has any "out" parameters, user execsql stmt */
if (is_assign)
{
appendStringInfoString(&func_inparas, "SELECT ");
}
else
{
appendStringInfoString(&func_inparas, "CALL ");
}
int tableof_func_dno = -1;
int tableof_var_dno = -1;
/*
* Properly double-quote schema name and function name to handle uppercase
* and special characters when making 'CALL func_name;' statement.
*/
quoted_sqlstart = NameListToQuotedString(funcname);
appendStringInfoString(&func_inparas, quoted_sqlstart);
pfree_ext(quoted_sqlstart);
appendStringInfoString(&func_inparas, "(");
/* analyse all parameters */
if (noargs)
{
i = 0;
tok = yylex();
}
else if (!multi_func)
{
if (p_argmodes)
{
for ( i = 0; i < narg ; i++)
{
initStringInfo(&argname);
pos_outer = -1;
if ('o' == p_argmodes[i] || 'b' == p_argmodes[i])
pos_inner++;
/*
* see if an arg is 'defargname => realargname' type.
* if so, p_argnames must be searched to match the defined
* argument name.
*/
if (plpgsql_is_token_match2(T_DATUM, PARA_EQUALS)
|| plpgsql_is_token_match2(T_WORD, PARA_EQUALS))
{
tok = yylex();
if (T_DATUM == tok)
appendStringInfoString(&argname, NameOfDatum(&yylval.wdatum));
else
appendStringInfoString(&argname, yylval.word.ident);
plpgsql_push_back_token(tok);
/*
* search the p_argnames to match the right argname with current arg,
* so the real postion of current arg can be determined
*/
bool argMatch = false;
for (j = 0; j < narg; j++)
{
if ('o' == p_argmodes[j] || 'b' == p_argmodes[j])
pos_outer++;
if (0 == pg_strcasecmp(argname.data, p_argnames[j]))
{
/*
* if argmodes is 'i', just append the text, else,
* row or rec should be assigned to store the out arg values
*/
switch (p_argmodes[j])
{
case 'i':
yylex_inparam(&func_inparas, &nparams, &tok, &tableof_func_dno, &tableof_var_dno);
check_tableofindex_args(tableof_var_dno, p_argtypes[j]);
break;
case 'o':
case 'b':
if (is_assign && 'o' == p_argmodes[j])
{
(void)yylex();
(void)yylex();
if (T_DATUM == (tok = yylex()))
{
plpgsql_push_back_token(tok);
(void)read_sql_expression2(',', ')', ",|)", &tok);
}
else
yyerror("syntax error");
break;
}
if (nparams)
appendStringInfoChar(&func_inparas, ',');
tok = yylex();
if (T_DATUM == tok)
appendStringInfoString(&func_inparas, NameOfDatum(&yylval.wdatum));
else
appendStringInfoString(&func_inparas, yylval.word.ident);
appendStringInfoString(&func_inparas, "=>");
/* pass => */
(void)yylex();
yylex_outparam(fieldnames, varnos, pos_outer, &row, &rec, &tok, true);
if ((!enable_out_param_override() && p_argmodes[j] == 'b')
|| T_DATUM == tok || T_VARRAY_VAR == tok
|| T_TABLE_VAR == tok || T_PACKAGE_VARIABLE == tok)
{
nfields++;
plpgsql_push_back_token(tok);
/* don't need inparam add ',' */
yylex_inparam(&func_inparas, NULL, &tok, &tableof_func_dno, &tableof_var_dno);
check_tableofindex_args(tableof_var_dno, p_argtypes[j]);
} else {
outParamInvalid = true;
}
nparams++;
break;
default:
const char* message = "parameter mode not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NODE_STATE),
errmsg("parameter mode %c doesn't exist", p_argmodes[j])));
}
argMatch = true;
break;
}
}
if (!argMatch) {
const char* message = "invoking function error,check function";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("when invoking function %s, no argments match \"%s\"", sqlstart, argname.data)));
}
if (outParamInvalid) {
const char* message = "invoking function error,check function";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("when invoking function %s, no destination for argments \"%s\"", sqlstart, argname.data)));
}
}
else
{
tok = yylex();
/* p_argmodes may be null, 'i'->in , 'o'-> out ,'b' inout,others error */
switch (p_argmodes[i])
{
case 'i':
if (T_PLACEHOLDER == tok)
placeholders++;
plpgsql_push_back_token(tok);
yylex_inparam(&func_inparas, &nparams, &tok, &tableof_func_dno, &tableof_var_dno);
check_tableofindex_args(tableof_var_dno, p_argtypes[i]);
break;
case 'o':
/*
* if the function is in an assign expr, just ignore the
* out parameters.
*/
if (is_assign)
{
if (T_DATUM == tok || T_VARRAY_VAR == tok || T_TABLE_VAR == tok || T_PACKAGE_VARIABLE == tok || T_PLACEHOLDER == tok)
{
plpgsql_push_back_token(tok);
(void)read_sql_expression2(',', ')', ",|)", &tok);
}
else
yyerror("syntax error");
break;
}
if (T_PLACEHOLDER == tok)
{
placeholders++;
}
else if (T_DATUM == tok || T_VARRAY_VAR == tok || T_TABLE_VAR == tok || T_PACKAGE_VARIABLE == tok || T_PLACEHOLDER == tok)
{
nfields++;
} else {
outParamInvalid = true;
}
plpgsql_push_back_token(tok);
yylex_outparam(fieldnames, varnos, pos_inner, &row, &rec, &tok, true);
plpgsql_push_back_token(tok);
yylex_inparam(&func_inparas, &nparams, &tok, &tableof_func_dno, &tableof_var_dno);
check_tableofindex_args(tableof_var_dno, p_argtypes[i]);
break;
case 'b':
if (is_assign)
{
if (!enable_out_param_override()
|| (enable_out_param_override()
&& (T_DATUM == tok || T_VARRAY_VAR == tok
|| T_TABLE_VAR == tok || T_PACKAGE_VARIABLE == tok)))
{
plpgsql_push_back_token(tok);
yylex_inparam(&func_inparas, &nparams, &tok, &tableof_func_dno, &tableof_var_dno);
}
else
yyerror("syntax error");
break;
}
if (T_PLACEHOLDER == tok)
placeholders++;
plpgsql_push_back_token(tok);
yylex_outparam(fieldnames, varnos, pos_inner, &row, &rec, &tok, true);
if (T_DATUM == tok || T_VARRAY_VAR == tok || T_TABLE_VAR == tok || T_PACKAGE_VARIABLE == tok) {
nfields++;
} else {
outParamInvalid = true;
}
plpgsql_push_back_token(tok);
yylex_inparam(&func_inparas, &nparams, &tok, &tableof_func_dno, &tableof_var_dno);
check_tableofindex_args(tableof_var_dno, p_argtypes[i]);
break;
default:
const char* message = "parameter mode not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NODE_STATE),
errmsg("parameter mode %c doesn't exist", p_argmodes[i])));
}
if (is_plpgsql_func_with_outparam && outParamInvalid) {
const char* message = "invoking function error,check function";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("when invoking function %s, no destination for argments \"%s\"", sqlstart, argname.data)));
}
}
if (')' == tok)
{
i++;
break;
}
if (narg - 1 == i) {
const char* message = "maybe input something superfluous";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("when invoking function %s, expected \")\", maybe input something superfluous.", sqlstart)));
}
if (',' != tok) {
const char* message = "invoking function error,check function";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("when invoking function %s, expected \",\"", sqlstart)));
}
pfree_ext(argname.data);
}
}
else
{
for ( i = 0; i < narg; i++)
{
tok = yylex();
if (T_PLACEHOLDER == tok)
placeholders++;
plpgsql_push_back_token(tok);
yylex_inparam(&func_inparas, &nparams, &tok, &tableof_func_dno, &tableof_var_dno);
if (')' == tok)
{
i++;
break;
}
if (narg - 1 == i) {
const char* message = "maybe input something superfluous";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("when invoking function %s, expected \")\", maybe input something superfluous.", sqlstart)));
}
}
}
}
else
{
while (true)
{
/* for named arguemnt */
if (plpgsql_is_token_match2(T_DATUM, PARA_EQUALS)
|| plpgsql_is_token_match2(T_WORD, PARA_EQUALS))
{
tok = yylex();
if (nparams)
appendStringInfoString(&func_inparas, ",");
if (T_DATUM == tok)
{
appendStringInfoString(&func_inparas, NameOfDatum(&yylval.wdatum));
namedargnamses[nfields] = pstrdup(NameOfDatum(&yylval.wdatum));
}
else
{
appendStringInfoString(&func_inparas, yylval.word.ident);
namedargnamses[nfields] = yylval.word.ident;
}
appendStringInfoString(&func_inparas, "=>");
(void)yylex();
int loc = yylex_outparam(fieldnames, varnos, nfields, &row, &rec, &tok, false, true);
if (tok == '(') {
int brackets = 1;
int syntaxErr = false;
// ignore record const value.
while(brackets != 0 && !syntaxErr) {
tok = yylex();
switch(tok) {
case '(':
brackets++;
break;
case ')':
brackets--;
break;
case 0:
syntaxErr = true;
break;
default:
;
}
}
if (syntaxErr) {
yyerror("Record/Composite type format error, brackets does't match.");
}
}
tok = yylex();
int curloc = yylloc;
plpgsql_push_back_token(tok);
plpgsql_append_source_text(&func_inparas, loc, curloc);
tok = yylex();
nparams++;
namedarg[nfields] = true;
}
else
{
yylex_outparam(fieldnames, varnos, nfields, &row, &rec, &tok, true, true);
plpgsql_push_back_token(tok);
yylex_inparam(&func_inparas, &nparams, &tok, &tableof_func_dno, &tableof_var_dno);
namedarg[nfields] = false;
namedargnamses[nfields] = NULL;
}
nfields++;
if (')' == tok)
{
break;
}
}
}
appendStringInfoString(&func_inparas, ")");
/* read the end token */
if ((tok = yylex()) != ';')
{
if (!is_assign)
{
const char* message = "maybe input something superfluous";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("when invoking function %s, maybe input something superfluous.", sqlstart)));
u_sess->plsql_cxt.have_error = true;
}
else
{
/* there may be other thing after the function invoke, just append it */
plpgsql_push_back_token(tok);
expr = read_sql_construct(';', 0, 0, ";", "", true, false, true, NULL, &tok);
appendStringInfoString(&func_inparas, expr->query);
pfree_ext(expr->query);
pfree_ext(expr);
}
}
if (!multi_func && inArgNum - i > ndefaultargs) {
const char* message = "function has no enough parameters";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function %s has no enough parameters", sqlstart)));
}
(void)MemoryContextSwitchTo(oldCxt);
/* generate the expression */
expr = (PLpgSQL_expr*)palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(func_inparas.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->ns = plpgsql_ns_top();
expr->idx = (uint32)-1;
expr->out_param_dno = -1;
expr->is_have_tableof_index_func = tableof_func_dno != -1 ? true : false;
if (multi_func)
{
PLpgSQL_function *function = NULL;
PLpgSQL_execstate *estate = (PLpgSQL_execstate*)palloc(sizeof(PLpgSQL_execstate));
expr->func = (PLpgSQL_function *) palloc0(sizeof(PLpgSQL_function));
function = expr->func;
function->fn_is_trigger = false;
function->fn_input_collation = InvalidOid;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = GetResolveOption();
function->fn_cxt = CurrentMemoryContext;
PLpgSQL_compile_context* curr_compile = u_sess->plsql_cxt.curr_compile_context;
estate->ndatums = curr_compile->plpgsql_nDatums;
estate->datums = (PLpgSQL_datum **)palloc(sizeof(PLpgSQL_datum *) * curr_compile->plpgsql_nDatums);
for (int i = 0; i < curr_compile->plpgsql_nDatums; i++)
estate->datums[i] = curr_compile->plpgsql_Datums[i];
function->cur_estate = estate;
function->cur_estate->func = function;
Oid funcid = getMultiFuncInfo(func_inparas.data, expr);
if (expr->func != NULL)
pfree_ext(expr->func);
if (estate->datums != NULL)
pfree_ext(estate->datums);
if (estate != NULL)
pfree_ext(estate);
expr->func = NULL;
int all_arg = 0;
if (OidIsValid(funcid))
{
proctup = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcid),
0, 0, 0);
/*
* function may be deleted after clist be searched.
*/
if (!HeapTupleIsValid(proctup))
{
const char* message = "function does not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function \"%s\" doesn't exist ", sqlstart)));
return NULL;
}
/* get the all args informations, only "in" parameters if p_argmodes is null */
all_arg = get_func_arg_info(proctup, &p_argtypes, &p_argnames, &p_argmodes);
procStruct = (Form_pg_proc) GETSTRUCT(proctup);
ndefaultargs = procStruct->pronargdefaults;
narg = procStruct->pronargs;
ReleaseSysCache(proctup);
for (int k = 0; k < all_arg; k++) {
check_tableofindex_args(varnos[k], p_argtypes[k]);
}
/* if there is no "out" parameters ,use perform stmt,others use exesql */
if ((0 == all_arg || NULL == p_argmodes))
{
PLpgSQL_stmt_perform *perform = NULL;
perform = (PLpgSQL_stmt_perform*)palloc0(sizeof(PLpgSQL_stmt_perform));
perform->cmd_type = PLPGSQL_STMT_PERFORM;
perform->lineno = plpgsql_location_to_lineno(location);
perform->expr = expr;
perform->sqlString = plpgsql_get_curline_query();
return (PLpgSQL_stmt *)perform;
}
else if (all_arg >= narg)
{
int out_arg_num = get_func_out_arg_num(p_argmodes, all_arg);
/* out arg number > 1 should build a row */
bool need_build_row = need_build_row_for_func_arg(&rec, &row, out_arg_num, all_arg, varnos, p_argmodes);
if (need_build_row)
{
int new_nfields = 0;
int i = 0, j = 0;
bool varnosInvalid = false;
for (i = 0; i < all_arg; i++)
{
if (p_argmodes[i] == 'i')
continue;
else
new_nfields++;
}
row = (PLpgSQL_row*)palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = pstrdup("*internal*");
row->lineno = plpgsql_location_to_lineno(location);
row->rowtupdesc = NULL;
row->nfields = new_nfields;
row->fieldnames = (char **)palloc0(sizeof(char *) * new_nfields);
row->varnos = (int *)palloc0(sizeof(int) * new_nfields);
row->isImplicit = true;
/* fetch out argument from fieldnames into row->fieldnames */
for (i = 0; i < nfields; i++)
{
if (namedarg[i] == false)
{
if (p_argmodes[i] == 'i')
continue;
else if (varnos[i] >= 0)
{
row->fieldnames[j] = fieldnames[i];
row->varnos[j] = varnos[i];
j++;
} else {
varnosInvalid = true;
}
}
else
{
char argmode = FUNC_PARAM_IN;
pos_outer = -1;
for (int curpos = 0; curpos < all_arg; curpos++)
{
char* paraname = p_argnames[curpos];
if (p_argmodes[curpos] != 'i')
pos_outer++;
else
{
continue;
}
if (paraname != NULL && !strcmp(paraname, namedargnamses[i]))
{
if (!p_argmodes)
argmode = FUNC_PARAM_IN;
else
argmode = p_argmodes[curpos];
break;
}
}
if (argmode == 'i')
continue;
if (fieldnames[i] == NULL)
{
const char* message = "Named argument can not be a const";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Named argument \"%s\" can not be a const", namedargnamses[i])));
}
if (row->varnos[pos_outer] > 0)
{
const char* message = "parameter is assigned more than once";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("parameter \"%s\" is assigned more than once", row->fieldnames[pos_outer])));
}
if (varnos[i] >= 0)
{
row->fieldnames[pos_outer] = fieldnames[i];
row->varnos[pos_outer] = varnos[i];
} else {
varnosInvalid = true;
}
}
}
if (varnosInvalid) {
pfree_ext(row->refname);
pfree_ext(row->fieldnames);
pfree_ext(row->varnos);
pfree_ext(row);
} else if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT) {
expr->out_param_dno = plpgsql_adddatum((PLpgSQL_datum *)row);
} else {
plpgsql_adddatum((PLpgSQL_datum *)row);
}
}
PLpgSQL_stmt_execsql * execsql = NULL;
execsql = (PLpgSQL_stmt_execsql *)palloc(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
execsql->lineno = plpgsql_location_to_lineno(location);
execsql->sqlstmt = expr;
execsql->into = row || rec ? true:false;
execsql->bulk_collect = false;
execsql->strict = true;
execsql->rec = rec;
execsql->row = row;
execsql->placeholders = placeholders;
execsql->multi_func = multi_func;
execsql->sqlString = plpgsql_get_curline_query();
return (PLpgSQL_stmt *)execsql;
}
}
else
{
const char* message = "function is not exclusive";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DUPLICATE_FUNCTION),
errmsg("function \"%s\" isn't exclusive ", sqlstart)));
}
}
else
{
/*
* if have out parameters and no row/rec, generate row type to store the results.
* if have row/rec and more than 2 out parameters , also generate row type.
*/
if (((nfields && NULL == rec && NULL == row) || nfields > 1) && !outParamInvalid)
{
row = (PLpgSQL_row*)palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = pstrdup("*internal*");
row->lineno = plpgsql_location_to_lineno(location);
row->rowtupdesc = NULL;
row->nfields = nfields;
row->fieldnames = (char **)palloc(sizeof(char *) * nfields);
row->varnos = (int *)palloc(sizeof(int) * nfields);
row->isImplicit = true;
while (--nfields >= 0)
{
row->fieldnames[nfields] = fieldnames[nfields];
row->varnos[nfields] = varnos[nfields];
}
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT) {
expr->out_param_dno = plpgsql_adddatum((PLpgSQL_datum *)row);
} else {
plpgsql_adddatum((PLpgSQL_datum *)row);
}
}
/* has invalid out param, set it to null */
if ((rec != NULL || row != NULL) && outParamInvalid)
{
rec = NULL;
row = NULL;
}
/* if there is no "out" parameters ,use perform stmt,others use exesql */
if (0 == narg || NULL == p_argmodes)
{
PLpgSQL_stmt_perform *perform = NULL;
perform = (PLpgSQL_stmt_perform*)palloc0(sizeof(PLpgSQL_stmt_perform));
perform->cmd_type = PLPGSQL_STMT_PERFORM;
perform->lineno = plpgsql_location_to_lineno(location);
perform->expr = expr;
perform->sqlString = plpgsql_get_curline_query();
return (PLpgSQL_stmt *)perform;
}
else
{
PLpgSQL_stmt_execsql * execsql = NULL;
execsql = (PLpgSQL_stmt_execsql *)palloc(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
execsql->lineno = plpgsql_location_to_lineno(location);
execsql->sqlstmt = expr;
execsql->into = row || rec ? true:false;
execsql->bulk_collect = false;
execsql->strict = true;
execsql->rec = rec;
execsql->row = row;
execsql->placeholders = placeholders;
execsql->multi_func = multi_func;
execsql->sqlString = plpgsql_get_curline_query();
return (PLpgSQL_stmt *)execsql;
}
}
pfree_ext(func_inparas.data);
/* skip compile warning */
return NULL;
}
static bool
is_unreserved_keyword_func(const char *name)
{
int i;
char *func_name[] = {"absolute", "alias", "backward", "constant","current", "debug", "detail","dump" ,"errcode","error", "exceptions", "first", "forward", "hint", "index", "info", "last", "log","message", "message_text", "multiset", "next", "no", "notice", "option", "package", "instantiation","pg_exception_context", "pg_exception_detail", "pg_exception_hint", "query", "record", "relative", "result_oid", "returned_sqlstate", "reverse","row_count","sroll", "slice", "stacked","sys_refcursor","use_column","use_variable","variable_conflict","varray","warning"};
for (i = 0; i <= 45; i++) {
if (pg_strcasecmp(func_name[i], name) == 0) {
return true;
}
}
return false;
}
/*
* Brief : check if it is an log function invoke
* Description :
* in keyword: keyword info
* in no_parenthesis: if no-parenthesis function is called.
* in name: the function name
* returns: true if is a log function.
* Notes :
*/
static bool
is_unreservedkeywordfunction(ScanKeyword* keyword, bool no_parenthesis, const char *name)
{
if (keyword && no_parenthesis && UNRESERVED_KEYWORD == keyword->category && !is_unreserved_keyword_func(name))
return false;
else
return true;
}
/*
* Brief : check if it is an function invoke
* Description :
* in name: the ident name
* in is_assign: is an assign stmt or not.
* in no_parenthesis: if no-parenthesis function is called.
* returns: true if is a function.
* Notes :
*/
static bool
is_function(const char *name, bool is_assign, bool no_parenthesis, List* funcNameList)
{
ScanKeyword * keyword = NULL;
List *funcname = NIL;
FuncCandidateList clist = NULL;
bool have_outargs = false;
bool have_inoutargs = false;
char **p_argnames = NULL;
char *p_argmodes = NULL;
Oid *p_argtypes = NULL;
HeapTuple proctup = NULL;
int narg = 0;
int i = 0;
char *cp[3] = {0};
/* the function is_function is only to judge if it's a function call, so memory used in it is all temp */
AutoContextSwitch plCompileCxtGuard(u_sess->plsql_cxt.curr_compile_context->compile_tmp_cxt);
if (funcNameList == NULL) {
plpgsql_parser_funcname(name, cp, 3);
}
/*
* if A.B.C, it's not a function invoke, because can not use function of
* other database. if A.B, then it must be a function invoke.
*/
if (cp[0] && cp[0][0] != '\0')
{
/* these are function in pg_proc who are overloaded, so they can not be
* execured by := or direct call.
*/
if (pg_strcasecmp("ts_stat", cp[0]) == 0 ||
pg_strcasecmp("ts_token_type", cp[0]) == 0 ||
pg_strcasecmp("ts_parse", cp[0]) == 0 ||
pg_strcasecmp("dblink_get_notify", cp[0]) == 0 ||
pg_strcasecmp("ts_debug", cp[0]) == 0)
return false;
keyword = (ScanKeyword *)ScanKeywordLookup(cp[0], ScanKeywords, NumScanKeywords);
/* function name can not be reserved keyword */
if (keyword && RESERVED_KEYWORD == keyword->category)
return false;
/* function name can not be unreserved keyword when no-parenthesis function is called. except log function*/
if (!is_unreservedkeywordfunction(keyword, no_parenthesis, cp[0]))
{
return false;
}
if (funcNameList == NULL) {
if (cp[2] && cp[2][0] != '\0')
funcname = list_make3(makeString(cp[0]), makeString(cp[1]),makeString(cp[2]));
else if (cp[1] && cp[1][0] != '\0')
funcname = list_make2(makeString(cp[0]), makeString(cp[1]));
else
funcname = list_make1(makeString(cp[0]));
/* search the function */
clist = FuncnameGetCandidates(funcname, -1, NIL, false, false, false);
} else {
clist = FuncnameGetCandidates(funcNameList, -1, NIL, false, false, false);
}
if (!clist)
{
if (!is_assign) {
const char* message = "function does not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function \"%s\" doesn't exist ", name)));
}
return false;
}
else if (clist->next)
{
if (is_assign)
return false;
else
return true;
}
else
{
proctup = SearchSysCache(PROCOID,
ObjectIdGetDatum(clist->oid),
0, 0, 0);
if (!HeapTupleIsValid(proctup))
{
if (!is_assign) {
const char* message = "function does not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function \"%s\" doesn't exist ", name)));
}
return false;
}
/* get the all args informations, only "in" parameters if p_argmodes is null */
narg = get_func_arg_info(proctup,&p_argtypes, &p_argnames, &p_argmodes);
if (p_argmodes)
{
for (i = 0; i < narg; i++)
{
if ('o' == p_argmodes[i])
{
have_outargs = true;
break;
}
}
for (i = 0; i < narg; i++)
{
if ('b' == p_argmodes[i])
{
have_inoutargs = true;
break;
}
}
}
ReleaseSysCache(proctup);
}
if (have_inoutargs && is_function_with_plpgsql_language_and_outparam(clist->oid))
return true;
if (!have_outargs && is_assign)
return false;
return true;/* passed all test */
} else if (funcNameList != NULL) {
clist = FuncnameGetCandidates(funcNameList, -1, NIL, false, false, false);
if (!clist)
{
if (!is_assign) {
const char* message = "function does not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function \"%s\" doesn't exist ", name)));
}
return false;
}
else if (clist->next)
{
if (is_assign)
return false;
else
return true;
}
else
{
proctup = SearchSysCache(PROCOID,
ObjectIdGetDatum(clist->oid),
0, 0, 0);
if (!HeapTupleIsValid(proctup))
{
if (!is_assign) {
const char* message = "function does not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function \"%s\" doesn't exist ", name)));
}
return false;
}
/* get the all args informations, only "in" parameters if p_argmodes is null */
narg = get_func_arg_info(proctup,&p_argtypes, &p_argnames, &p_argmodes);
if (p_argmodes)
{
for (i = 0; i < narg; i++)
{
if ('o' == p_argmodes[i])
{
have_outargs = true;
break;
}
}
for (i = 0; i < narg; i++)
{
if ('b' == p_argmodes[i])
{
have_inoutargs = true;
break;
}
}
}
ReleaseSysCache(proctup);
}
if (have_inoutargs && is_function_with_plpgsql_language_and_outparam(clist->oid))
return true;
if (!have_outargs && is_assign)
return false;
return true;/* passed all test */
}
return false;
}
static void checkFuncName(List* funcname)
{
MemoryContext colCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_tmp_cxt);
char* name = NameListToString(funcname);
Oid packageOid = InvalidOid;
Oid namespaceId = InvalidOid;
if (list_length(funcname) > 2) {
if (list_length(funcname) == 3) {
char* schemaname = strVal(linitial(funcname));
char* pkgname = strVal(lsecond(funcname));
namespaceId = SchemaNameGetSchemaOid(schemaname, false);
if (OidIsValid(namespaceId)) {
packageOid = PackageNameGetOid(pkgname, namespaceId);
}
}
if (!OidIsValid(packageOid)) {
const char* message = "function does not exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: %s", name)));
u_sess->plsql_cxt.have_error = true;
}
}
(void)MemoryContextSwitchTo(colCxt);
}
/*
* @brief is_datatype
* check if a given type is a datatype
* @param name constructed type name
* @return true is datatype
* @return false not a datatype
*/
static bool is_paren_friendly_datatype(TypeName *name)
{
Type type_tup = LookupTypeNameExtended(NULL, name, NULL, false);
if (!HeapTupleIsValid(type_tup)) {
return false;
}
ReleaseSysCache(type_tup);
/* deal with internal type casts, e.g. timestamp(6) */
Oid typoid = LookupTypeNameOid(name);
bool preferred = false;
char category = TYPCATEGORY_INVALID;
get_type_category_preferred(typoid, &category, &preferred);
if (category != TYPCATEGORY_ARRAY && category != TYPCATEGORY_COMPOSITE
&& category != TYPCATEGORY_TABLEOF && category != TYPCATEGORY_TABLEOF_VARCHAR
&& category != TYPCATEGORY_TABLEOF_INTEGER) {
return false;
}
return true;
}
/*
* @brief init_array_parse_context
* Initialize array parse context.
*/
static inline void init_array_parse_context(ArrayParseContext *context)
{
context->list_left_bracket = NULL;
context->list_right_bracket = NULL;
context->list_array_state = NULL;
context->list_datatype = NULL;
context->array_is_empty = false;
context->array_is_nested = false;
}
/*
* @brief push_array_parse_stack
* Push array parse context stacks.
* @param context array parse context
* @param parenlevel parentheses level, set -1 to push nothing
* @param state array state flag, set -1 to push nothing
*/
static inline void push_array_parse_stack(ArrayParseContext *context, int parenlevel, int state)
{
if (likely(parenlevel >= 0)) {
context->list_left_bracket = lcons_int(parenlevel, context->list_left_bracket);
context->list_right_bracket = lcons_int(parenlevel, context->list_right_bracket);
}
if (state >= 0) {
context->list_array_state = lcons_int(state, context->list_array_state);
}
}
/*
* @brief construct_array_start
* Extracted from read_sql_constuct, start of T_VARRAY/T_TABLE array FSM.
* @param ds sql string builder
* @param context array state context
* @param var_type element type
* @param tok current token, should be T_VARRAY/T_TABLE
* @param parenlevel parentheses stack
* @param loc T_VARRAY starting location
*/
static bool construct_array_start(StringInfo ds, ArrayParseContext *context, PLpgSQL_type *var_type, int *tok,
int parenlevel, int loc)
{
/* Save array token for datatype casts */
context->list_datatype = lcons(var_type, context->list_datatype);
*tok = yylex(); /* always yylex to parentheses */
plpgsql_push_back_token(*tok);
/* varray constructor */
if (*tok == '(') {
appendStringInfoString(ds, "ARRAY");
push_array_parse_stack(context, parenlevel, ARRAY_START);
} else if (*tok == '[') {
yyerror("syntax error"); /* typename + '[' is not allowed */
return false; /* compiler happy */
} else { /* coerce? no need for extra check, leave it to main parser */
plpgsql_append_source_text(ds, loc, yylloc);
}
return true;
}
/*
* @brief get_real_elemtype
* Get the real element type from a table or array
* @param type any type
* @return PLpgSQL_type* element type
*/
static PLpgSQL_type *get_real_elemtype(PLpgSQL_type *type)
{
HeapTuple type_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type->typoid));
if (!HeapTupleIsValid(type_tup)) {
ereport(ERROR, (errmodule(MOD_PLSQL),
errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for type %u, type Oid is invalid", type->typoid)));
}
if (((Form_pg_type)GETSTRUCT(type_tup))->typcategory == TYPCATEGORY_ARRAY &&
type->collectionType == PLPGSQL_COLLECTION_NONE) {
type->collectionType = PLPGSQL_COLLECTION_ARRAY;
}
if (type->collectionType != PLPGSQL_COLLECTION_TABLE &&
type->collectionType != PLPGSQL_COLLECTION_ARRAY) {
/* Guarding condition(white list) */
ReleaseSysCache(type_tup);
return type;
}
PLpgSQL_type* typ = NULL;
Oid base_oid = ((Form_pg_type)GETSTRUCT(type_tup))->typelem;
HeapTuple base_type_tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(base_oid));
if (!HeapTupleIsValid(base_type_tup)) {
ereport(ERROR, (errmodule(MOD_PLSQL),
errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for type %u, type Oid is invalid", base_oid)));
}
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile == NULL)
{
typ = build_datatype(base_type_tup, type->atttypmod, 0);
} else {
typ = build_datatype(base_type_tup, type->atttypmod,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_input_collation);
}
typ->collectionType = type->collectionType;
ReleaseSysCache(base_type_tup);
ReleaseSysCache(type_tup);
pfree_ext(type);
return typ;
}
/*
* @brief construct_object_type
* Construct object type to strings.
* @param ds sql string builder
* @param context array state context
* @param name object typename
* @param tok current token, should be T_WORD/T_CWORD
* @param parenlevel parentheses stack
* @param curloc current parsing location, usually the following token
* @param loc T_WORD/T_CWORD starting location
*/
static bool construct_object_type(StringInfo ds, ArrayParseContext *context, TypeName *name, int *tok,
int parenlevel, int curloc, int loc)
{
if (*tok != '(' || u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
curloc = yylloc; /* new token location */
plpgsql_append_source_text(ds, loc, curloc);
context->array_is_nested = false;
return true;
}
if (context->array_is_nested) {
push_array_parse_stack(context, parenlevel, ARRAY_ACCESS);
curloc = yylloc; /* new token location */
plpgsql_append_source_text(ds, loc, curloc);
context->array_is_nested = false;
return true;
}
/*
* Support various datatype constructor. We expect the T_WORD with format like:
* 'var := foo(bar)' or inside other objects.
*/
char *name_str = NameListToString(name->names);
int toksz = strlen(name_str);
if (yylloc - curloc != toksz || !is_paren_friendly_datatype(name)) { /* NOTE: curloc here is T_WORD/T_CWORD location */
curloc = yylloc; /* new token location */
plpgsql_append_source_text(ds, loc, curloc);
pfree_ext(name_str);
return true;
}
PLpgSQL_type *type = parse_datatype(name_str, loc);\
pfree_ext(name_str);
type = get_real_elemtype(type);
if (type->collectionType == PLPGSQL_COLLECTION_ARRAY || \
type->collectionType == PLPGSQL_COLLECTION_TABLE) {
return construct_array_start(ds, context, type, tok, parenlevel, loc);
}
appendStringInfoString(ds, "ROW");
return true;
}
/*
* @brief construct_word
* Construct T_WORD, extracted from read_sql_constuct.
* @param ds sql string builder
* @param context array state context
* @param tok current token, should be T_WORD
* @param parenlevel parentheses stack
* @param loc location of T_WORD
*/
static bool construct_word(StringInfo ds, ArrayParseContext *context, int *tok, int parenlevel, int loc)
{
char *name = NULL;
name = yylval.word.ident;
int curloc = yylloc;
*tok = yylex();
plpgsql_push_back_token(*tok);
return construct_object_type(ds, context, makeTypeName(name), tok, parenlevel, curloc, loc);
}
/*
* @brief construct_cword
* Construct T_CWORD, extracted from read_sql_constuct.
* @param ds sql string builder
* @param context array state context
* @param tok current token, should be T_CWORD
* @param parenlevel parentheses stack
* @param loc location of T_CWORD
*/
static bool construct_cword(StringInfo ds, ArrayParseContext *context, int *tok, int parenlevel, int loc)
{
int nnames = 0;
List *idents = yylval.cword.idents;
char *word1 = NULL;
char *word2 = NULL;
char *word3 = NULL;
if (list_length(idents) == 2) {
word1 = strVal(linitial(idents));
word2 = strVal(lsecond(idents));
} else if (list_length(idents) == 3) {
word1 = strVal(linitial(idents));
word2 = strVal(lsecond(idents));
word3 = strVal(lthird(idents));
} else {
yyerror("syntax error");
}
if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
int dno = -1;
char *name = NameListToString(idents);
if (plpgsql_ns_lookup(plpgsql_ns_top(), false, word1, word2, word3, &nnames) == NULL) {
dno = plpgsql_pkg_add_unknown_var_to_namespace(yylval.cword.idents);
}
if (dno != -1) {
appendStringInfoString(ds, name);
appendStringInfoString(ds, " ");
}
pfree_ext(name);
/* This originally was a fall through to default branch */
*tok = yylex();
int curloc = yylloc;
plpgsql_push_back_token(*tok);
plpgsql_append_source_text(ds, loc, curloc);
return true;
}
int curloc = yylloc;
*tok = yylex();
plpgsql_push_back_token(*tok);
return construct_object_type(ds, context, makeTypeNameFromNameList(idents), tok, parenlevel, curloc, loc);
}
/* 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);
}
/*
* Brief : read a sql construct who has 4 expected tokens.
* Description : just like read_sql_construct function below, except that
* this function has 4 expected tokens.
* Notes :
*/
/*
* 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)
* until4: token code for alternate terminator (pass 0 if none)
* until5: token code for alternate terminator (pass 0 if none)
* expected: text to use in complaining that terminator was not found
* sqlstart: text to prefix to the accumulated SQL text
* isexpression: whether to say we're reading an "expression" or a "statement"
* valid_sql: whether to check the syntax of the expr (prefixed with sqlstart)
* 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_construct6(int until,
int until2,
int until3,
int until4,
int until5,
int until6,
const char *expected,
const char *sqlstart,
bool isexpression,
bool valid_sql,
bool trim,
int *startloc,
int *endtoken,
DList* tokenstack,
bool issaveexpr)
{
int tok = 0;
int prev_tok = 0;
StringInfoData ds;
IdentifierLookup save_IdentifierLookup;
int startlocation = -1;
int parenlevel = 0;
PLpgSQL_expr *expr;
MemoryContext oldCxt = NULL;
/*
* buf stores the cursor attribute internal variables or
* varray ident with operate function, so NAMEDATALEN + 128 is enough
*/
char buf[NAMEDATALEN + 128];
bool ds_changed = false;
ArrayParseContext context;
List *idents = 0;
const char left_bracket[2] = "[";
const char right_bracket[2] = "]";
const char left_parentheses[2] = "(";
const char right_parentheses[2] = ")";
int loc = 0;
int curloc = 0;
int brack_cnt = 0;
/* mark if there are 2 table of index by var call functions in an expr */
int tableof_func_dno = -1;
int tableof_var_dno = -1;
bool is_have_tableof_index_var = false;
List* tableof_index_list = NIL;
PLpgSQL_compile_context* curr_compile = u_sess->plsql_cxt.curr_compile_context;
/*
* ds will do a lot of enlarge, which need to relloc the space, and the old space
* will be return to current context. So if we don't switch the context, the original
* context will be Plpgsql function context, which has a long term life cycle,
* may cause memory accumulation.
*/
oldCxt = MemoryContextSwitchTo(curr_compile->compile_tmp_cxt);
initStringInfo(&ds);
MemoryContextSwitchTo(oldCxt);
appendStringInfoString(&ds, sqlstart);
/* special lookup mode for identifiers within the SQL text */
save_IdentifierLookup = curr_compile->plpgsql_IdentifierLookup;
curr_compile->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
init_array_parse_context(&context);
for (;;)
{
prev_tok = tok;
tok = yylex();
tokenstack = push_token_stack(tok, tokenstack);
loc = yylloc;
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 == until4 && parenlevel == 0)
break;
if (tok == until5 && parenlevel == 0)
break;
if (tok == until6 && parenlevel == 0)
break;
if (tok == '(' || tok == '[')
parenlevel++;
else if (tok == ')' || tok == ']')
{
parenlevel--;
if (parenlevel < 0)
yyerror("mismatched parentheses", true);
}
/*
* 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", true);
if (isexpression) {
const char* message = "missing something at end of SQL expression";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("missing \"%s\" at end of SQL expression",
expected),
parser_errposition(yylloc)));
} else {
const char* message = "missing something at end of SQL expression";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("missing \"%s\" at end of SQL statement",
expected),
parser_errposition(yylloc)));
}
}
switch (tok)
{
case T_SQL_FOUND:
appendStringInfo(&ds, " __gsdb_sql_cursor_attri_found__ ");
ds_changed = true;
break;
case T_CURSOR_FOUND:
appendStringInfo(&ds, " __gsdb_cursor_attri_%d_found__ ", yylval.ival);
ds_changed = true;
break;
case T_SQL_NOTFOUND:
appendStringInfo(&ds, " __gsdb_sql_cursor_attri_notfound__ ");
ds_changed = true;
break;
case T_CURSOR_NOTFOUND:
appendStringInfo(&ds, " __gsdb_cursor_attri_%d_notfound__ ", yylval.ival);
ds_changed = true;
break;
case T_SQL_ISOPEN:
appendStringInfo(&ds, " __gsdb_sql_cursor_attri_isopen__ ");
ds_changed = true;
break;
case T_CURSOR_ISOPEN:
appendStringInfo(&ds, " __gsdb_cursor_attri_%d_isopen__ ", yylval.ival);
ds_changed = true;
break;
case T_SQL_ROWCOUNT:
appendStringInfo(&ds, " __gsdb_sql_cursor_attri_rowcount__ ");
ds_changed = true;
break;
case T_CURSOR_ROWCOUNT:
appendStringInfo(&ds, " __gsdb_cursor_attri_%d_rowcount__ ", yylval.ival);
ds_changed = true;
break;
case T_PACKAGE_CURSOR_NOTFOUND:
appendStringInfo(&ds, " %s.__gsdb_cursor_attri_%d_notfound__ ",
yylval.wdatum.ident, yylval.wdatum.dno);
ds_changed = true;
break;
case T_PACKAGE_CURSOR_ISOPEN:
appendStringInfo(&ds, " %s.__gsdb_cursor_attri_%d_isopen__ ",
yylval.wdatum.ident, yylval.wdatum.dno);
ds_changed = true;
break;
case T_PACKAGE_CURSOR_ROWCOUNT:
appendStringInfo(&ds, " %s.__gsdb_cursor_attri_%d_rowcount__ ",
yylval.wdatum.ident, yylval.wdatum.dno);
ds_changed = true;
break;
case T_PACKAGE_CURSOR_FOUND:
appendStringInfo(&ds, " %s.__gsdb_cursor_attri_%d_found__ ",
yylval.wdatum.ident, yylval.wdatum.dno);
ds_changed = true;
break;
case T_VARRAY_VAR:
idents = yylval.wdatum.idents;
if (idents == NIL) {
AddNamespaceIfPkgVar(yylval.wdatum.ident, save_IdentifierLookup);
}
tok = yylex();
if (tok == '(' || tok == '[') {
push_array_parse_stack(&context, parenlevel, ARRAY_ACCESS);
}
curloc = yylloc;
plpgsql_push_back_token(tok);
if (list_length(idents) >= 3) {
plpgsql_cast_reference_list(idents, &ds, false);
ds_changed = true;
break;
} else {
plpgsql_append_source_text(&ds, loc, curloc);
ds_changed = true;
break;
}
case T_ARRAY_FIRST:
{
Oid indexType = get_table_index_type(yylval.wdatum.datum, &tableof_func_dno);
if (indexType == VARCHAROID) {
appendStringInfo(&ds, "ARRAY_VARCHAR_FIRST(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents, false);
} else if (indexType == INT4OID) {
appendStringInfo(&ds, "ARRAY_INTEGER_FIRST(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents, false);
} else {
appendStringInfo(&ds, "ARRAY_LOWER(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
appendStringInfo(&ds, "1 ");
}
int tok = yylex();
if(tok == '(') {
push_array_parse_stack(&context, -1, NOT_AN_ARRAY);
parenlevel++;
ds_changed = true;
tokenstack = push_token_stack(tok, tokenstack);
} else {
appendStringInfo(&ds, ") ");
ds_changed = true;
plpgsql_push_back_token(tok);
}
break;
}
case T_ARRAY_LAST:
{
Oid indexType = get_table_index_type(yylval.wdatum.datum, &tableof_func_dno);
if (indexType == VARCHAROID) {
appendStringInfo(&ds, "ARRAY_VARCHAR_LAST(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents, false);
} else if (indexType == INT4OID) {
appendStringInfo(&ds, "ARRAY_INTEGER_LAST(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents, false);
} else {
appendStringInfo(&ds, "ARRAY_UPPER(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
appendStringInfo(&ds, "1 ");
}
int tok = yylex();
if(tok == '(') {
push_array_parse_stack(&context, -1, NOT_AN_ARRAY);
parenlevel++;
ds_changed = true;
tokenstack = push_token_stack(tok, tokenstack);
} else {
appendStringInfo(&ds, ") ");
ds_changed = true;
plpgsql_push_back_token(tok);
}
break;
}
case T_ARRAY_COUNT:
{
Oid indexType = get_table_index_type(yylval.wdatum.datum, &tableof_func_dno);
if (indexType == VARCHAROID || indexType == INT4OID) {
appendStringInfo(&ds, "ARRAY_INDEXBY_LENGTH(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
appendStringInfo(&ds, "1 ");
} else {
appendStringInfo(&ds, "ARRAY_LENGTH(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
appendStringInfo(&ds, "1 ");
}
int tok = yylex();
if(tok == '(') {
push_array_parse_stack(&context, -1, NOT_AN_ARRAY);
parenlevel++;
ds_changed = true;
tokenstack = push_token_stack(tok, tokenstack);
} else {
appendStringInfo(&ds, ") ");
ds_changed = true;
plpgsql_push_back_token(tok);
}
break;
}
case T_ARRAY_EXISTS:
{
Oid indexType = get_table_index_type(yylval.wdatum.datum, &tableof_func_dno);
if (indexType == VARCHAROID) {
appendStringInfo(&ds, "ARRAY_VARCHAR_EXISTS(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
} else if (indexType == INT4OID) {
appendStringInfo(&ds, "ARRAY_INTEGER_EXISTS(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
} else {
appendStringInfo(&ds, "ARRAY_EXISTS(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
}
int tok = yylex();
if('(' == tok) {
push_array_parse_stack(&context, -1, NOT_AN_ARRAY);
parenlevel++;
ds_changed = true;
tokenstack = push_token_stack(tok, tokenstack);
} else {
plpgsql_push_back_token(tok);
yyerror("syntax error");
}
break;
}
case T_ARRAY_PRIOR:
{
Oid indexType = get_table_index_type(yylval.wdatum.datum, &tableof_func_dno);
if (indexType == VARCHAROID) {
appendStringInfo(&ds, "ARRAY_VARCHAR_PRIOR(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
} else if (indexType == INT4OID) {
appendStringInfo(&ds, "ARRAY_INTEGER_PRIOR(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
} else {
appendStringInfo(&ds, "ARRAY_PRIOR(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
}
int tok = yylex();
if('(' == tok) {
push_array_parse_stack(&context, -1, NOT_AN_ARRAY);
parenlevel++;
ds_changed = true;
tokenstack = push_token_stack(tok, tokenstack);
} else {
plpgsql_push_back_token(tok);
yyerror("syntax error");
}
break;
}
case T_ARRAY_NEXT:
{
Oid indexType = get_table_index_type(yylval.wdatum.datum, &tableof_func_dno);
if (indexType == VARCHAROID) {
appendStringInfo(&ds, "ARRAY_VARCHAR_NEXT(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
} else if (indexType == INT4OID) {
appendStringInfo(&ds, "ARRAY_INTEGER_NEXT(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
} else {
appendStringInfo(&ds, "ARRAY_NEXT(");
CastArrayNameToArrayFunc(&ds, yylval.wdatum.idents);
}
int tok = yylex();
if('(' == tok) {
push_array_parse_stack(&context, -1, NOT_AN_ARRAY);
parenlevel++;
ds_changed = true;
tokenstack = push_token_stack(tok, tokenstack);
} else {
plpgsql_push_back_token(tok);
yyerror("syntax error");
}
break;
}
case ']':
brack_cnt--;
/* fall through */
case ')':
if (context.list_right_bracket && context.list_right_bracket->length
&& linitial_int(context.list_right_bracket) == parenlevel) {
/* append bracket instead of parentheses */
appendStringInfoString(&ds, right_bracket);
ds_changed = true;
context.list_right_bracket = list_delete_first(context.list_right_bracket);
if (context.array_is_empty) {
/* NOTE: empty array state will remain ARRAY_START until now */
PLpgSQL_type *type = (PLpgSQL_type *)linitial(context.list_datatype);
plpgsql_append_object_typename(&ds, type);
appendStringInfoString(&ds, left_bracket);
appendStringInfoString(&ds, right_bracket);
context.array_is_empty = false;
}
/*
* N-D Array accessing pattern:
* If another pair of brackets(parentheses) are right behind an array
* access statment like 'array(i)' or 'array[i], they are very likely
* to be part of the array statement too. Therefore, we need to make
* sure we interpret them as brackets.
*/
if (IS_ARRAY_STATE(context.list_array_state, ARRAY_ACCESS)) {
tok = yylex();
plpgsql_push_back_token(tok);
if (tok == '(' || tok == '[') { /* array(1)(1) */
push_array_parse_stack(&context, parenlevel, -1);
break;
} else if (tok == '.') { /* array(1).col(1) */
context.array_is_nested = true;
}
} else {
/* It is important to pop the array dno stack at the end. */
context.list_datatype = list_delete_first(context.list_datatype);
}
/* always pop the array state stack, including ARRAY_ACCESS */
context.list_array_state = list_delete_first(context.list_array_state);
} else {
/* pop excess array state stack, see case '(' below */
if (IS_ARRAY_STATE(context.list_array_state, NOT_AN_ARRAY)) {
context.list_array_state = list_delete_first(context.list_array_state);
}
if (tok ==']') {
appendStringInfoString(&ds, right_bracket);
} else {
appendStringInfoString(&ds, right_parentheses);
}
}
ds_changed = true;
break;
case '[':
brack_cnt++;
/* fall through */
case '(':
if (context.list_left_bracket && context.list_left_bracket->length
&& linitial_int(context.list_left_bracket) == parenlevel - 1) {
appendStringInfoString(&ds, left_bracket);
context.list_left_bracket = list_delete_first(context.list_left_bracket);
/* in the case of '()' or '[]', we need to append NULL */
prev_tok = tok;
tok = yylex();
plpgsql_push_back_token(tok);
if ((prev_tok == '(' && tok == ')') || (prev_tok == '[' && tok == ']')) {
if (IS_ARRAY_STATE(context.list_array_state, ARRAY_ACCESS)) {
yyerror("empty index");
#ifndef ENABLE_MULTIPLE_NODES
if (u_sess->attr.attr_common.plsql_show_all_error) {
u_sess->plsql_cxt.have_error = true;
tok = yylex(); // lex ) or ]
tok = yylex(); // lex ;
return NULL;
}
#endif
}
context.array_is_empty = true;
} else if (IS_ARRAY_STATE(context.list_array_state, ARRAY_START)) {
SET_ARRAY_STATE(context.list_array_state, ARRAY_ELEMENT);
/* always append left parentheses at start of each element */
appendStringInfoString(&ds, left_parentheses);
}
} else {
/* array state stack up with none */
if (IS_ARRAY_STATE(context.list_array_state, ARRAY_ELEMENT) ||
IS_ARRAY_STATE(context.list_array_state, NOT_AN_ARRAY)) {
context.list_array_state = lcons_int(NOT_AN_ARRAY, context.list_array_state);
}
if (tok =='[') {
appendStringInfoString(&ds, left_bracket);
} else {
appendStringInfoString(&ds, left_parentheses);
}
}
ds_changed = true;
break;
case '.':
tok = yylex();
plpgsql_push_back_token(tok);
curloc = yylloc;
if (context.array_is_nested && tok != T_WORD && tok != T_CWORD) {
context.list_array_state = list_delete_first(context.list_array_state);
context.array_is_nested = false;
}
plpgsql_append_source_text(&ds, loc, curloc);
ds_changed = true;
break;
case T_TABLE_VAR:
{
/*
* table var name may be schema.pkg.table_var
* so the name length should NAMEDATALEN with quotation marks *3
*/
const int tablevar_namelen = NAMEDATALEN * 3 + 6;
char tableName1[tablevar_namelen] = {0};
idents = yylval.wdatum.idents;
if (idents == NIL) {
AddNamespaceIfPkgVar(yylval.wdatum.ident, save_IdentifierLookup);
}
copy_table_var_indents(tableName1, yylval.wdatum.ident, tablevar_namelen);
PLpgSQL_datum* datum = yylval.wdatum.datum;
int var_dno = yylval.wdatum.dno;
if (datum->dtype == PLPGSQL_DTYPE_VAR) {
check_autonomous_nest_tablevar((PLpgSQL_var*)datum);
}
tok = yylex();
if('(' == tok) {
push_array_parse_stack(&context, parenlevel, ARRAY_ACCESS);
tableof_index_list = lappend_int(tableof_index_list, ((PLpgSQL_var*)datum)->dno);
} else if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && K_MULTISET == tok) {
Oid typeOid = get_table_type(datum);
read_multiset(&ds, tableName1, typeOid);
ds_changed = true;
break;
} else {
PLpgSQL_var* var = (PLpgSQL_var*)datum;
if (OidIsValid(var->datatype->tableOfIndexType) &&
(',' == tok || ')' == tok || ';' == tok)) {
is_have_tableof_index_var = true;
/* tableof_var_dno is only used for args */
if (',' == tok || ')' == tok) {
tableof_var_dno = var_dno;
}
}
}
curloc = yylloc;
plpgsql_push_back_token(tok);
if (list_length(idents) >= 3) {
plpgsql_cast_reference_list(idents, &ds, false);
ds_changed = true;
break;
} else {
plpgsql_append_source_text(&ds, loc, curloc);
ds_changed = true;
break;
}
}
case T_PACKAGE_VARIABLE:
{
int type_flag = -1;
get_datum_tok_type(yylval.wdatum.datum, &type_flag);
idents = yylval.wdatum.idents;
if (type_flag == PLPGSQL_TOK_TABLE_VAR) {
/*
* table var name may be schema.pkg.table_var
* so the name length should NAMEDATALEN with quotation marks *3
*/
const int tablevar_namelen = NAMEDATALEN * 3 + 6;
char tableName1[tablevar_namelen] = {0};
idents = yylval.wdatum.idents;
copy_table_var_indents(tableName1, yylval.wdatum.ident, tablevar_namelen);
PLpgSQL_datum* datum = yylval.wdatum.datum;
tok = yylex();
if('(' == tok)
{
push_array_parse_stack(&context, parenlevel, NOT_AN_ARRAY);
} else if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && K_MULTISET == tok) {
Oid typeOid = get_table_type(datum);
read_multiset(&ds, tableName1, typeOid);
ds_changed = true;
break;
}
curloc = yylloc;
plpgsql_push_back_token(tok);
if (PkgVarNeedCast(idents)) {
plpgsql_cast_reference_list(idents, &ds, true);
} else {
plpgsql_append_source_text(&ds, loc, curloc);
}
ds_changed = true;
break;
} else if (type_flag == PLPGSQL_TOK_VARRAY_VAR) {
tok = yylex();
if (tok == '(' || tok == '[') {
push_array_parse_stack(&context, parenlevel, ARRAY_ACCESS);
}
curloc = yylloc;
plpgsql_push_back_token(tok);
if (PkgVarNeedCast(idents)) {
plpgsql_cast_reference_list(idents, &ds, true);
} else {
plpgsql_append_source_text(&ds, loc, curloc);
}
ds_changed = true;
break;
} else {
tok = yylex();
curloc = yylloc;
plpgsql_push_back_token(tok);
if (PkgVarNeedCast(idents)) {
plpgsql_cast_reference_list(idents, &ds, true);
} else {
plpgsql_append_source_text(&ds, loc, curloc);
}
ds_changed = true;
break;
}
}
case T_TABLE:
{
int dno = yylval.wdatum.datum->dno;
PLpgSQL_var *var = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[dno];
/* table of index by only support: a = a(); */
if ((OidIsValid(var->datatype->tableOfIndexType) && var->nest_table == NULL) ||
(var->nest_table != NULL && var->isIndexByTblOf)) {
TokenData* temptokendata = build_token_data(tok);
int first_tok = yylex();
DList* tokenstack = NULL;
TokenData* temptokendata1 = build_token_data(first_tok);
tokenstack = dlappend(tokenstack, temptokendata1);
if (first_tok != '(') {
yyerror("table of index by does not support syntax");
}
int second_tok = yylex();
temptokendata1 = build_token_data(second_tok);
if (second_tok != ')') {
yyerror("table of index by does not support syntax");
}
tokenstack = dlappend(tokenstack, temptokendata1);
/* restore yylex */
push_back_token_stack(tokenstack);
yylloc = temptokendata->lloc;
yylval = temptokendata->lval;
u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng = temptokendata->leng;
}
ds_changed = construct_array_start(&ds, &context, var->datatype, &tok, parenlevel, loc);
break;
}
case T_VARRAY:
{
if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
/* non A_FORMAT case, append 'NULL' and leave it. */
int first_tok = yylex();
tokenstack= push_token_stack(first_tok, tokenstack);
int second_tok = yylex();
tokenstack = push_token_stack(first_tok, tokenstack);
if (first_tok == '(' && second_tok == ')') {
snprintf(buf, sizeof(buf), " NULL ");
appendStringInfoString(&ds, buf);
}
ds_changed = true;
break;
}
int dno = yylval.wdatum.datum->dno;
PLpgSQL_var *var = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[dno];
ds_changed = construct_array_start(&ds, &context, var->datatype, &tok, parenlevel, loc);
break;
}
case T_DATUM:
idents = yylval.wdatum.idents;
if(prev_tok != '.' && list_length(idents) >= 3) {
plpgsql_cast_reference_list(idents, &ds, false);
ds_changed = true;
break;
} else {
tok = yylex();
curloc = yylloc;
plpgsql_push_back_token(tok);
plpgsql_append_source_text(&ds, loc, curloc);
ds_changed = true;
break;
}
case T_WORD:
AddNamespaceIfPkgVar(yylval.word.ident, save_IdentifierLookup);
ds_changed = construct_word(&ds, &context, &tok, parenlevel, loc);
break;
case T_CWORD:
ds_changed = construct_cword(&ds, &context, &tok, parenlevel, loc);
break;
default:
tok = yylex();
if(tok > INT_MAX)
{
const char* message = "token value is bigger than INT_MAX";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("token value %d is bigger than INT_MAX ", tok)));
}
curloc = yylloc;
plpgsql_push_back_token(tok);
if (tok == T_VARRAY_VAR && pushed_bulk_exception()) {
plpgsql_append_source_text(&ds, loc, curloc - 4);
} else {
plpgsql_append_source_text(&ds, loc, curloc);
}
ds_changed = true;
break;
}
/* peek one token */
tok = yylex();
plpgsql_push_back_token(tok);
/* we are expecting an element, but a seperator/end of array is found, need coerce */
if ((tok == ',' || tok == ')') && IS_ARRAY_STATE(context.list_array_state, ARRAY_ELEMENT)) {
SET_ARRAY_STATE(context.list_array_state, ARRAY_COERCE);
}
/* this is done after seperator is appended, get next element */
if (IS_ARRAY_STATE(context.list_array_state, ARRAY_SEPERATOR)) {
/* always append left parentheses at start of each element */
appendStringInfoString(&ds, left_parentheses);
/* Note: we assume that all seperators takes EXACTLY ONE iteration to process */
SET_ARRAY_STATE(context.list_array_state, ARRAY_ELEMENT);
}
/* add coerce at the end of each element, then append the seperator next iteration */
if (IS_ARRAY_STATE(context.list_array_state, ARRAY_COERCE)) {
/* always append right parentheses at end of each element */
appendStringInfoString(&ds, right_parentheses);
plpgsql_append_object_typename(&ds, (PLpgSQL_type *)linitial(context.list_datatype));
SET_ARRAY_STATE(context.list_array_state, ARRAY_SEPERATOR);
}
}
if (brack_cnt != 0) {
yyerror("mismatched brackets");
}
curr_compile->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");
}
if (!ds_changed)
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';
}
if (issaveexpr) {
oldCxt = MemoryContextSwitchTo(curr_compile->compile_cxt);
}
if (tableof_index_list != NULL && tableof_func_dno != -1) {
ListCell* cell = NULL;
foreach (cell, tableof_index_list) {
int dno = lfirst_int(cell);
if (dno != tableof_func_dno) {
yyerror("do not support more than 2 table of index by variables call functions in an expr");
}
}
}
list_free_ext(tableof_index_list);
expr = (PLpgSQL_expr *)palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->ns = plpgsql_ns_top();
expr->isouttype = false;
expr->idx = (uint32)-1;
expr->out_param_dno = -1;
expr->is_have_tableof_index_var = is_have_tableof_index_var;
expr->tableof_var_dno = tableof_var_dno;
expr->is_have_tableof_index_func = tableof_func_dno != -1 ? true : false;
expr->tableof_func_dno = tableof_func_dno;
pfree_ext(ds.data);
if (valid_sql)
check_sql_expr(expr->query, startlocation, strlen(sqlstart));
if (issaveexpr) {
MemoryContextSwitchTo(oldCxt);
}
return expr;
}
/*
* read multiset grammer
*/
static void read_multiset(StringInfoData* ds, char* tableName1, Oid typeOid1)
{
/*
* table var name may be schema.pkg.table_var
* so the name length should NAMEDATALEN with quotation marks *3
*/
const int tablevar_namelen = NAMEDATALEN * 3 + 6;
/* len = funcname + tablename1 + tablename2 */
const int funclen = tablevar_namelen * 2 + 128;
char funcName[funclen] = {0};
int tok1 = yylex();
errno_t rc = EOK;
bool isTableVar = false;
int tok_type = -1;
switch (tok1)
{
case K_UNION:
rc = snprintf_s(funcName, sizeof(funcName), sizeof(funcName) - 1, " array_union");
securec_check_ss(rc, "", "");
break;
case K_INTERSECT:
rc = snprintf_s(funcName, sizeof(funcName), sizeof(funcName) - 1, " array_intersect");
securec_check_ss(rc, "", "");
break;
case K_EXCEPT:
rc = snprintf_s(funcName, sizeof(funcName), sizeof(funcName) - 1, " array_except");
securec_check_ss(rc, "", "");
break;
default:
yyerror("unexpected keyword after multiset.");
}
int suffixlen = tablevar_namelen * 2 + 64;
char suffix[suffixlen] = {0};
int tok2 = yylex();
int tok3;
Oid typeOid2 = InvalidOid;
char tableName2[tablevar_namelen] = {0};
if (K_DISTINCT == tok2) {
tok3 = yylex();
tok_type = -1;
if (tok3 == T_PACKAGE_VARIABLE) {
get_datum_tok_type(yylval.wdatum.datum, &tok_type);
}
isTableVar = (tok3 == T_TABLE_VAR) || (tok_type == PLPGSQL_TOK_TABLE_VAR);
if (isTableVar) {
typeOid2 = get_table_type(yylval.wdatum.datum);
copy_table_var_indents(tableName2, yylval.wdatum.ident, tablevar_namelen);
rc = snprintf_s(suffix, sizeof(suffix), sizeof(suffix) - 1, "_distinct(\"%s\", \"%s\")", tableName1, tableName2);
securec_check_ss(rc, "", "");
} else {
yyerror("unexpected keyword after distinct.");
}
} else if (K_ALL == tok2) {
tok3 = yylex();
tok_type = -1;
if (tok3 == T_PACKAGE_VARIABLE) {
get_datum_tok_type(yylval.wdatum.datum, &tok_type);
}
isTableVar = (tok3 == T_TABLE_VAR) || (tok_type == PLPGSQL_TOK_TABLE_VAR);
if (isTableVar) {
typeOid2 = get_table_type(yylval.wdatum.datum);
copy_table_var_indents(tableName2, yylval.wdatum.ident, tablevar_namelen);
rc = snprintf_s(suffix, sizeof(suffix), sizeof(suffix) - 1, "(\"%s\", \"%s\")", tableName1, tableName2);
securec_check_ss(rc, "", "");
} else {
yyerror("unexpected keyword after all.");
}
} else {
tok_type = -1;
if (tok2 == T_PACKAGE_VARIABLE) {
get_datum_tok_type(yylval.wdatum.datum, &tok_type);
}
isTableVar = (tok2 == T_TABLE_VAR) || (tok_type == PLPGSQL_TOK_TABLE_VAR);
if (isTableVar) {
typeOid2 = get_table_type(yylval.wdatum.datum);
copy_table_var_indents(tableName2, yylval.wdatum.ident, tablevar_namelen);
rc = snprintf_s(suffix, sizeof(suffix), sizeof(suffix) - 1, "(\"%s\", \"%s\")", tableName1, tableName2);
securec_check_ss(rc, "", "");
} else {
yyerror("unexpected type after multiset.");
}
}
if (typeOid1 != typeOid2) {
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("%s and %s type are not match.", tableName1, tableName2)));
u_sess->plsql_cxt.have_error = true;
}
strlcat(funcName, suffix, sizeof(funcName));
appendStringInfoString(ds, funcName);
}
static bool copy_table_var_indents(char* tableName, char* idents, int tablevar_namelen)
{
List* namelist = NIL;
ListCell* l = NULL;
errno_t rc = 0;
StringInfoData ds;
initStringInfo(&ds);
if (!SplitIdentifierString(idents, '.', &namelist))
ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("invalid name syntax")));
if (namelist == NIL)
ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("invalid name syntax")));
foreach (l, namelist) {
char* curname = (char*)lfirst(l);
appendStringInfoString(&ds, curname);
if (l->next != NULL) {
appendStringInfoString(&ds, "\".\"");
}
}
rc = snprintf_s(tableName, tablevar_namelen, tablevar_namelen - 1, "%s", ds.data);
securec_check_ss(rc, "", "");
pfree_ext(ds.data);
list_free_ext(namelist);
return true;
}
/*
* 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)
{
return read_sql_construct6(until, until2, until3, until3, 0, 0,
expected, sqlstart, isexpression, valid_sql, trim, startloc, endtoken);
}
/*
* get function declare or definition string in package.
*/
static char *
get_proc_str(int tok)
{
int blocklevel = 0;
int pre_tok = 0;
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package == NULL) {
yyerror("not allowed create procedure in function or procedure.");
}
tok = yylex();
StringInfoData ds;
bool is_defining_proc = false;
char * spec_proc_str = NULL;
int loc = yylloc;
int curloc = yylloc;
MemoryContext oldCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_tmp_cxt);
initStringInfo(&ds);
MemoryContextSwitchTo(oldCxt);
if (u_sess->parser_cxt.is_procedure==false){
appendStringInfoString(&ds, " CREATE OR REPLACE FUNCTION ");
} else {
appendStringInfoString(&ds, " CREATE OR REPLACE PROCEDURE ");
}
u_sess->parser_cxt.in_package_function_compile = true;
while(true)
{
if (tok == YYEOF) {
if (u_sess->parser_cxt.is_procedure==false){
yyerror("function is not ended correctly");
break;
} else {
yyerror("procedure is not ended correctly");
break;
}
}
if (tok == ';' && !is_defining_proc)
{
break;
}
/* procedure or function can have multiple blocklevel*/
if (tok == K_BEGIN)
{
blocklevel++;
}
/* if have is or as,it means in body*/
if ((tok == K_IS || tok == K_AS) && !is_defining_proc)
{
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->is_spec_compiling) {
yyerror("not allow define function or procedure in package specification");
break;
}
is_defining_proc = true;
}
if (tok == K_END)
{
tok = yylex();
/* process end loop*/
if (tok == K_LOOP) {
continue;
}
if (blocklevel == 1 && (pre_tok == ';' || pre_tok == K_BEGIN) && (tok == ';' || tok == 0))
{
curloc = yylloc;
plpgsql_append_source_text(&ds, loc, curloc);
break;
}
if (blocklevel > 1 && (pre_tok == ';' || pre_tok == K_BEGIN) && (tok == ';' || tok == 0))
{
blocklevel--;
}
}
pre_tok = tok;
tok = yylex();
curloc = yylloc;
plpgsql_append_source_text(&ds, loc, curloc);
loc = yylloc;
}
u_sess->parser_cxt.in_package_function_compile = false;
/*
To use the SQL Parser, we add a string to make SQL parser recognize.
Package specification use SQL parser parse the string too.
*/
if (is_defining_proc == false) {
appendStringInfoString(&ds, " AS BEGIN ");
appendStringInfoString(&ds, " END;\n");
u_sess->parser_cxt.isFunctionDeclare = true;
} else {
u_sess->parser_cxt.isFunctionDeclare = false;
}
spec_proc_str = pstrdup(ds.data);
pfree_ext(ds.data);
return spec_proc_str;
}
static char* get_init_proc(int tok)
{
int startlocation = yylloc;
char* init_package_str = NULL;
StringInfoData ds;
initStringInfo(&ds);
while(true)
{
/*
package instantiation still need a token 'END' as end,
but it's from package end, For details about how to instantiate
a package, see the package instantiation grammer.
*/
if (tok == K_BEGIN)
startlocation = yylloc;
if (tok == YYEOF) {
yyerror("procedure is not ended correctly");
break;
}
if (tok == K_END)
{
tok = yylex();
if (tok == ';' || tok == 0)
{
break;
}
}
tok = yylex();
}
/*
package instantiation not need specify end.
*/
plpgsql_append_source_text(&ds, startlocation, yylloc);
init_package_str = pstrdup(ds.data);
pfree_ext(ds.data);
return init_package_str;
}
/* get assign attrname of arrayelement */
static char * get_attrname(int tok)
{
if (tok == YYEMPTY || tok == '.') {
tok = plpgsql_yylex_single();
}
plpgsql_location_to_lineno(yylloc);
switch (tok) {
case T_ARRAY_FIRST:
case T_ARRAY_LAST:
case T_ARRAY_COUNT:
case T_ARRAY_EXTEND:
case T_CWORD:/* composite name not OK */
yyerror("syntax error");
break;
case T_REFCURSOR:
case T_VARRAY:
case T_VARRAY_VAR:
case T_TABLE:
case T_TABLE_VAR:
case T_RECORD:
case T_DATUM:
if (yylval.wdatum.idents != NIL) { /* composite name not OK */
yyerror("syntax error");
} else {
return yylval.wdatum.ident;
}
break;
case T_WORD:
return yylval.word.ident;
break;
default:
if (plpgsql_is_token_keyword(tok)) {
return pstrdup(yylval.keyword);
} else {
yyerror("missing or illegal attribute name");
}
break;
}
return NULL;
}
static PLpgSQL_type *
read_datatype(int tok)
{
StringInfoData ds;
char *type_name;
int startlocation;
PLpgSQL_type *result = NULL;
int parenlevel = 0;
/* Should only be called while parsing DECLARE sections */
AssertEreport(u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE,
MOD_PLSQL,
"Should only be called while parsing DECLARE sections");
/* 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.
*/
bool iskeyword = plpgsql_is_token_keyword(tok);
if (tok == T_WORD || tok == T_VARRAY_VAR || tok == T_TABLE_VAR || iskeyword)
{
char *dtname;
if (iskeyword) {
dtname = pstrdup(yylval.keyword);
} else if (tok == T_WORD) {
dtname = yylval.word.ident;
} else {
dtname = yylval.wdatum.ident;
}
tok = yylex();
if (tok == '%')
{
tok = yylex();
if (tok_is_keyword(tok, &yylval,
K_TYPE, "type"))
{
result = plpgsql_parse_wordtype(dtname);
if (result) {
if(iskeyword) {
pfree_ext(dtname);
}
return result;
}
}
else if (tok_is_keyword(tok, &yylval,
K_ROWTYPE, "rowtype"))
{
PLpgSQL_nsitem *ns;
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
dtname, NULL, NULL, NULL);
if (ns && ns->itemtype == PLPGSQL_NSTYPE_VAR)
{
PLpgSQL_var* var = (PLpgSQL_var*)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[ns->itemno];
if(var && var->datatype
&& var->datatype->typoid == REFCURSOROID)
{
#ifndef ENABLE_MULTIPLE_NODES
if (OidIsValid(var->datatype->cursorCompositeOid))
return build_type_from_cursor_var(var);
else
#endif
return plpgsql_build_datatype(RECORDOID, -1, InvalidOid);
}
} else if (ns && ns->itemtype == PLPGSQL_NSTYPE_ROW)
{
PLpgSQL_row* row = (PLpgSQL_row*)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[ns->itemno];
if (row && row->rowtupdesc && row->rowtupdesc->tdtypeid != RECORDOID)
{
if (OidIsValid(row->rowtupdesc->tdtypeid))
return plpgsql_build_datatype(row->rowtupdesc->tdtypeid, -1, InvalidOid);
}
}
result = plpgsql_parse_wordrowtype(dtname);
if (result) {
if(iskeyword) {
pfree_ext(dtname);
}
return result;
}
}
}
if(iskeyword) {
pfree_ext(dtname);
}
}
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"))
{
/* find val.col%TYPE first */
HeapTuple tup = NULL;
tup = FindRowVarColType(dtnames);
if (tup != NULL) {
Oid typOid = typeTypeId(tup);
ReleaseSysCache(tup);
return plpgsql_build_datatype(typOid, -1, InvalidOid);
}
/* find pkg.var%TYPE second */
PLpgSQL_datum* datum = GetPackageDatum(dtnames);
if (datum != NULL && datum->dtype == PLPGSQL_DTYPE_VAR) {
Oid typOid = ((PLpgSQL_var*)datum)->datatype->typoid;
int32 typmod = ((PLpgSQL_var*)datum)->datatype->atttypmod;
Oid collation = ((PLpgSQL_var*)datum)->datatype->collation;
return plpgsql_build_datatype(typOid, typmod, collation);
}
result = plpgsql_parse_cwordtype(dtnames);
if (result)
return result;
}
else if (tok_is_keyword(tok, &yylval,
K_ROWTYPE, "rowtype"))
{
PLpgSQL_datum* datum = GetPackageDatum(dtnames);
if (datum != NULL && datum->dtype == PLPGSQL_DTYPE_ROW) {
PLpgSQL_row* row = (PLpgSQL_row*)datum;
if (row->rowtupdesc && row->rowtupdesc->tdtypeid != RECORDOID)
{
if (OidIsValid(row->rowtupdesc->tdtypeid))
return plpgsql_build_datatype(row->rowtupdesc->tdtypeid, -1, InvalidOid);
}
}
result = plpgsql_parse_cwordrowtype(dtnames);
if (result)
return result;
}
}
}
while (tok != ';')
{
if (tok == 0)
{
if (parenlevel != 0)
yyerror("mismatched parentheses", true);
else
yyerror("incomplete data type declaration");
break;
}
/* Possible followers for datatype in a declaration */
if (tok == K_COLLATE || tok == K_NOT ||
tok == '=' || tok == COLON_EQUALS || tok == K_DEFAULT || tok == K_INDEX)
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') {
#ifndef ENABLE_MULTIPLE_NODES
if (u_sess->attr.attr_common.plsql_show_all_error) {
const char* message = "missing data type declaration";
InsertErrorMessage(message, yylloc);
}
#endif
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("missing data type declaration"),
parser_errposition(yylloc)));
if (tok == ';') {
plpgsql_push_back_token(tok);
}
} else {
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
result = parse_datatype(type_name, startlocation);
pfree_ext(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_row *row = NULL;
PLpgSQL_rec *rec = NULL;
int tok;
int prev_tok;
int prev_prev_tok = 0;
int curloc;
bool have_into = false;
bool have_bulk_collect = false;
bool have_strict = false;
bool array_begin = false;
int into_start_loc = -1;
int into_end_loc = -1;
int placeholders = 0;
int parenlevel = 0;
List *list_bracket = 0; /* stack structure bracket tracker */
List *list_bracket_loc = 0; /* location tracker */
initStringInfo(&ds);
/* special lookup mode for identifiers within the SQL text */
save_IdentifierLookup = u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup;
u_sess->plsql_cxt.curr_compile_context->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. ALTER TABLE PARTITION MERGE or SPLIT INTO.
* We just check for ALTER as the command's first token.
*
* 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 support InsertStmt:Insert into table_name values record_var */
bool insert_stmt = false;
bool prev_values = false;
bool insert_record = false;
bool insert_array_record = false;
int values_end_loc = -1;
int before_semi_loc = -1;
PLpgSQL_row* row_data = NULL;
PLpgSQL_rec* rec_data = NULL;
PLpgSQL_var* array_data = NULL;
for (;;)
{
prev_tok = tok;
tok = yylex();
if (tok == COMMENTSTRING) {
prev_prev_tok = prev_tok;
}
if (have_into && into_end_loc < 0)
into_end_loc = yylloc; /* token after the INTO part */
/* Only support one record variable after insertstmt's values token */
if (insert_record && tok != ';' && tok != ' ') {
yyerror("unexpected record variable number in insert stmt.");
}
if (tok == ';') {
before_semi_loc = yylloc;
break;
}
if (tok == 0)
yyerror("unexpected end of function definition");
if(tok == T_PLACEHOLDER)
{
placeholders++;
}
if (tok == K_BULK) {
/* only A_FORMAT can use bulk collect into */
if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
yyerror("syntax error");
}
have_bulk_collect = true;
into_start_loc = yylloc; /* save BULK COLLECT INTO starting point */
tok = yylex();
if (tok != K_COLLECT) {
yyerror("expect \'COLLECT\' after \'BULK\'");
}
tok = yylex();
if (tok != K_INTO) {
yyerror("expect \'INTO\' after \'BULK COLLECT\'");
}
}
if (tok == K_INTO)
{
if (prev_tok == K_INSERT || (prev_tok == COMMENTSTRING && prev_prev_tok == K_INSERT)) {
insert_stmt = true;
continue; /* INSERT INTO is not an INTO-target */
}
if (firsttoken == K_ALTER)
continue; /* ALTER ... INTO is not an INTO-target */
if (prev_tok == K_MERGE)
continue; /* MERGE INTO is not an INTO-target */
if (have_into)
yyerror("INTO specified more than once");
have_into = true;
if (!have_bulk_collect) {
into_start_loc = yylloc;
}
u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
read_into_target(&rec, &row, &have_strict, have_bulk_collect);
u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
}
if (tok == T_CWORD && prev_tok!=K_SELECT
&& prev_tok!= K_PERFORM) {
List *dtname = yylval.cword.idents;
tok = yylex();
if (tok == '(') {
plpgsql_push_back_token(tok);
continue;
}
plpgsql_push_back_token(tok);
plpgsql_pkg_add_unknown_var_to_namespace(dtname);
continue;
}
/*
* A_FORMAT VARRAY related handling
* To make parentheses-based varray access compatible with current
* PL/pgSQL array, we need to replace all parentheses with brackets.
* Like what we did in read_sql_contruct5(), we interpret the input
* query with array states.
*
* When array_begin is set to true, it means we have an array comming
* in next iteration, we need to append a bracket instead of parentheses.
* Or, array_begin is set to false that the query statement will remain
* unchanged.
*/
if (u_sess->attr.attr_sql.sql_compatibility != A_FORMAT) {
continue;
}
/* Find 'values' position for InsertStmt */
if (insert_stmt && tok == T_WORD) {
if (pg_strcasecmp(yylval.word.ident, "values") == 0) {
tok = yylex();
if (tok == '(') {
prev_values = false;
} else {
prev_values = true;
}
plpgsql_push_back_token(tok);
continue;
}
}
/* Check the variable type after 'values' */
if (insert_stmt && prev_values && (tok == T_WORD || tok == T_VARRAY_VAR || tok == T_TABLE_VAR)) {
if (tok == T_WORD) {
PLpgSQL_nsitem* ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, yylval.word.ident, NULL, NULL, NULL);
if (ns == NULL) {
yyerror("insert an nonexistent variable.");
}
PLpgSQL_datum* datum = u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[ns->itemno];
insert_record = true;
prev_values = false;
values_end_loc = yylloc;
if (PLPGSQL_DTYPE_RECORD == datum->dtype
|| PLPGSQL_DTYPE_ROW == datum->dtype) {
row_data = (PLpgSQL_row*)datum;
continue;
} else if (PLPGSQL_DTYPE_REC == datum->dtype) {
rec_data = (PLpgSQL_rec*)datum;
if (rec_data->tupdesc == NULL) {
yyerror("unsupported insert into table from record type without desc, "
"may need set behavior_compat_options to allow_procedure_compile_check.");
}
continue;
}
yyerror("unsupported insert into table from non record type.");
} else if (tok == T_VARRAY_VAR || tok == T_TABLE_VAR) {
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR) {
array_data = (PLpgSQL_var *) yylval.wdatum.datum;
insert_array_record = true;
prev_values = false;
values_end_loc = yylloc;
continue;
}
yyerror("unsupported insert into table from non record type.");
} else {
yyerror("unsupported insert into table from non record type.");
}
}
if (tok == '(' || tok == '[')
parenlevel++;
else if (tok == ')' || tok == ']')
{
parenlevel--;
if (parenlevel < 0)
yyerror("mismatched parentheses", true);
}
switch(tok) {
case T_VARRAY_VAR:
curloc = yylloc; /* always save current location before yylex() */
tok = yylex();
if (tok == '(' || tok == '[')
array_begin = true;
plpgsql_push_back_token(tok);
break;
case T_TABLE_VAR:
curloc = yylloc; /* always save current location before yylex() */
tok = yylex();
if (tok == '(')
array_begin = true;
else if (tok == '[')
yyerror("syntax error");
plpgsql_push_back_token(tok);
break;
case ']':
case ')':
if (list_bracket && linitial_int(list_bracket) == parenlevel) {
prev_tok = tok;
curloc = yylloc; /* always save current location before yylex() */
tok = yylex();
if (tok == '(' || tok == '[' || tok == '.') {
array_begin = true; /* N-D array access */
}
plpgsql_push_back_token(tok);
/* stack pop */
list_bracket = list_delete_first(list_bracket);
if (prev_tok == ')') {
/* append location of right bracket */
list_bracket_loc = lappend_int(list_bracket_loc, curloc - location);
}
}
break;
case '[':
case '(':
if (array_begin) {
/* stack push */
list_bracket = lcons_int(parenlevel - 1, list_bracket);
if (tok == '(') {
/* cancat location of left bracket */
list_bracket_loc = lcons_int(yylloc - location, list_bracket_loc);
}
array_begin = false;
}
break;
case T_WORD:
case T_CWORD:
if (array_begin) {
curloc = yylloc; /* always save current location before yylex() */
tok = yylex();
if (tok != '(' && tok != '[') {
array_begin = false;
}
plpgsql_push_back_token(tok);
}
break;
case '.':
if (array_begin) {
curloc = yylloc; /* always save current location before yylex() */
tok = yylex();
if (tok != T_WORD && tok != T_CWORD) {
array_begin = false;
}
plpgsql_push_back_token(tok);
}
break;
default:
/* do nothing */
break;
}
}
u_sess->plsql_cxt.curr_compile_context->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);
if (parenlevel != 0 ||
list_length(list_bracket) != 0 ||
list_length(list_bracket_loc) % 2)
{
yyerror("mismatched parentheses");
}
plpgsql_process_stmt_array(&ds, list_bracket_loc);
list_free_ext(list_bracket_loc);
} else if (insert_record) {
/* Support grammar 'Insert into table_name values record_type' in Procedure.
* because SQL Parser does's support that, so record type will be broken into
* record variable's sub attribute variable for 'Insert into table_name
* values(var,...).
*/
plpgsql_append_source_text(&ds, location, values_end_loc);
appendStringInfoString(&ds, "(");
if (row_data != NULL) {
int nfields = row_data->nfields;
for (int i = 0; i < nfields; i++) {
appendStringInfo(&ds, "%s.%s", row_data->refname, row_data->fieldnames[i]);
if (i != (nfields - 1)) {
appendStringInfoString(&ds, ",");
}
}
} else {
int nattr = rec_data->tupdesc->natts;
for (int i = 0; i < nattr; i++) {
Form_pg_attribute pg_att_form = rec_data->tupdesc->attrs[i];
appendStringInfo(&ds, "%s.%s", rec_data->refname, NameStr(pg_att_form->attname));
if (i != (nattr - 1)) {
appendStringInfoString(&ds, ",");
}
}
}
appendStringInfoString(&ds, ")");
} else if (insert_array_record) {
/* Support grammar 'Insert into table_name values array[][]...' in Procedure.
* because SQL Parser does's support that, so record type will be broken into
* record variable's sub attribute variable for 'Insert into table_name
* values(array[][].var,...).
*/
Oid base_type_oid = get_element_type(array_data->datatype->typoid);
if (!OidIsValid(base_type_oid)) {
yyerror("invalid array's element type for insert.");
}
HeapTuple type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(base_type_oid));
if (!HeapTupleIsValid(type_tuple)) {
yyerror("invalid array's element type tuple for insert.");
}
Form_pg_type type_pg_type = (Form_pg_type)GETSTRUCT(type_tuple);
int rel_oid = type_pg_type->typrelid;
ReleaseSysCache(type_tuple);
HeapTuple rel_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rel_oid));
if (!HeapTupleIsValid(rel_tuple)) {
yyerror("invalid type's rel tuple for insert.", true);
u_sess->plsql_cxt.have_error = true;
} else {
plpgsql_append_source_text(&ds, location, values_end_loc);
appendStringInfoString(&ds, "(");
int replace_start_poc = ds.len;
Form_pg_class rel_pg_class = (Form_pg_class) GETSTRUCT(rel_tuple);
int att_num = rel_pg_class->relnatts;
ReleaseSysCache(rel_tuple);
for (int i = 1; i <= att_num; i++) {
HeapTuple att_tuple = SearchSysCache2(ATTNUM, ObjectIdGetDatum(rel_oid), Int16GetDatum(i));
if (!HeapTupleIsValid(att_tuple)) {
yyerror("invalid element att tuple for insert.");
}
Form_pg_attribute att_pg_attribute = (Form_pg_attribute)GETSTRUCT(att_tuple);
plpgsql_append_source_text(&ds, values_end_loc, before_semi_loc);
appendStringInfo(&ds, ".%s", att_pg_attribute->attname.data);
if (i != att_num) {
appendStringInfoString(&ds, ",");
}
ReleaseSysCache(att_tuple);
}
int replace_end_poc = ds.len - 1;
appendStringInfoString(&ds, ")");
int bracket_level = 0;
/* replace table/array's '('/')' to '['/']' SQL supported */
for (int i = replace_start_poc; i <= replace_end_poc; i++) {
if (ds.data[i] == '(') {
bracket_level++;
if (bracket_level == 1) {
ds.data[i] = '[';
}
} else if (ds.data[i] == ')') {
if (bracket_level == 1) {
ds.data[i] = ']';
}
bracket_level--;
}
}
}
} else {
plpgsql_append_source_text(&ds, location, yylloc);
if (parenlevel != 0 ||
list_length(list_bracket) != 0 ||
list_length(list_bracket_loc) % 2)
{
yyerror("mismatched parentheses");
}
plpgsql_process_stmt_array(&ds, list_bracket_loc);
list_free_ext(list_bracket_loc);
}
/* trim any trailing whitespace, for neatness */
while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
ds.data[--ds.len] = '\0';
expr = (PLpgSQL_expr *)palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->ns = plpgsql_ns_top();
expr->idx = (uint32)-1;
expr->out_param_dno = -1;
pfree_ext(ds.data);
check_sql_expr(expr->query, location, 0);
#ifndef ENABLE_MULTIPLE_NODES
if (firsttoken == K_SELECT && u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && ALLOW_PROCEDURE_COMPILE_CHECK) {
(void)getCursorTupleDesc(expr, false, true);
}
#endif
execsql = (PLpgSQL_stmt_execsql *)palloc(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
execsql->lineno = plpgsql_location_to_lineno(location);
execsql->sqlstmt = expr;
execsql->into = have_into;
execsql->bulk_collect = have_bulk_collect;
execsql->strict = have_strict;
execsql->rec = rec;
execsql->row = row;
execsql->placeholders = placeholders;
execsql->sqlString = plpgsql_get_curline_query();
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;
/* set direction defaults: */
fetch->direction = FETCH_FORWARD;
fetch->how_many = 1;
fetch->expr = NULL;
fetch->returns_multiple_rows = false;
fetch->bulk_collect = false;
fetch->has_direction = true;
fetch->sqlString = plpgsql_get_curline_query();
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 || tok == T_PACKAGE_VARIABLE)
{
/* Assume there's no direction clause and tok is a cursor name */
plpgsql_push_back_token(tok);
fetch->has_direction = false;
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 *newp;
int token = -1;
newp = (PLpgSQL_stmt_return *)palloc0(sizeof(PLpgSQL_stmt_return));
newp->cmd_type = PLPGSQL_STMT_RETURN;
newp->lineno = plpgsql_location_to_lineno(location);
newp->expr = NULL;
newp->retvarno = -1;
newp->sqlString = plpgsql_get_curline_query();
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_retset)
{
if (yylex() != ';') {
const char* message = "RETURN cannot have a parameter in function returning set";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN cannot have a parameter in function returning set"),
errhint("Use RETURN NEXT or RETURN QUERY."),
parser_errposition(yylloc)));
}
}
// adapting A db, where return value is independent from OUT parameters
else if (';' == (token = yylex()) && u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->out_param_varno >= 0)
newp->retvarno = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->out_param_varno;
else
{
plpgsql_push_back_token(token);
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_rettype == VOIDOID)
{
if (yylex() != ';') {
const char* message = "RETURN cannot have a parameter in function returning void";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN cannot have a parameter in function returning void"),
parser_errposition(yylloc)));
}
}
else if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_retistuple)
{
int tok = yylex();
if(tok < 0)
{
const char* message = "token value is smaller than 0 ";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("token value %d is smaller than 0 ", tok)));
return NULL;
}
switch (tok)
{
case K_NULL:
/* we allow this to support RETURN NULL in triggers */
break;
case T_DATUM:
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_RECORD)
newp->retvarno = yylval.wdatum.dno;
else {
const char* message = "RETURN must specify a record or row variable in function returning row";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN must specify a record or row variable in function returning row"),
parser_errposition(yylloc)));
}
break;
default:
const char* message = "RETURN must specify a record or row variable in function returning row";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN must specify a record or row variable in function returning row"),
parser_errposition(yylloc)));
break;
}
if (yylex() != ';')
yyerror("syntax error");
}
else
{
/*
* Note that a well-formed expression is _required_ here;
* anything else is a compile-time error.
*/
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT) {
newp->retvarno = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->out_param_varno;
}
newp->expr = read_sql_expression(';', ";");
if (newp->expr->is_have_tableof_index_var) {
yyerror("table of index type is not supported as function return type.");
}
}
}
return (PLpgSQL_stmt *) newp;
}
static PLpgSQL_stmt *
make_return_next_stmt(int location)
{
PLpgSQL_stmt_return_next *newp;
if (!u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_retset) {
const char* message = "cannot use RETURN NEXT in a non-SETOF function";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot use RETURN NEXT in a non-SETOF function"),
parser_errposition(location)));
}
newp = (PLpgSQL_stmt_return_next *)palloc0(sizeof(PLpgSQL_stmt_return_next));
newp->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
newp->lineno = plpgsql_location_to_lineno(location);
newp->expr = NULL;
newp->retvarno = -1;
newp->sqlString = plpgsql_get_curline_query();
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->out_param_varno >= 0)
{
if (yylex() != ';') {
const char* message = "RETURN NEXT cannot have a parameter in function with OUT parameters";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN NEXT cannot have a parameter in function with OUT parameters"),
parser_errposition(yylloc)));
}
newp->retvarno = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->out_param_varno;
}
else if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_retistuple)
{
switch (yylex())
{
case T_DATUM:
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_RECORD ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
newp->retvarno = yylval.wdatum.dno;
else {
const char* message = "RETURN NEXT must specify a record or row variable in function returning row";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN NEXT must specify a record or row variable in function returning row"),
parser_errposition(yylloc)));
}
break;
default:
const char* message = "RETURN NEXT must specify a record or row variable in function returning row";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("RETURN NEXT must specify a record or row variable in function returning row"),
parser_errposition(yylloc)));
break;
}
if (yylex() != ';')
yyerror("syntax error");
}
else
newp->expr = read_sql_expression(';', ";");
return (PLpgSQL_stmt *) newp;
}
static PLpgSQL_stmt *
make_return_query_stmt(int location)
{
PLpgSQL_stmt_return_query *newp;
int tok;
if (!u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_retset) {
const char* message = "cannot use RETURN QUERY in a non-SETOF function";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot use RETURN QUERY in a non-SETOF function"),
parser_errposition(location)));
}
newp = (PLpgSQL_stmt_return_query *)palloc0(sizeof(PLpgSQL_stmt_return_query));
newp->cmd_type = PLPGSQL_STMT_RETURN_QUERY;
newp->lineno = plpgsql_location_to_lineno(location);
newp->sqlString = plpgsql_get_curline_query();
/* check for RETURN QUERY EXECUTE */
if ((tok = yylex()) != K_EXECUTE)
{
/* ordinary static query */
plpgsql_push_back_token(tok);
newp->query = read_sql_stmt("");
}
else
{
/* dynamic SQL */
int term;
newp->dynquery = read_sql_expression2(';', K_USING, "; or USING",
&term);
if (term == K_USING)
{
do
{
PLpgSQL_expr *expr;
expr = read_sql_expression2(',', ';', ", or ;", &term);
newp->params = lappend(newp->params, expr);
} while (term == ',');
}
}
return (PLpgSQL_stmt *) newp;
}
/* convenience routine to fetch the name of a T_DATUM */
static char *
NameOfDatum(PLwdatum *wdatum)
{
if (wdatum->ident)
return wdatum->ident;
AssertEreport(wdatum->idents != NIL,
MOD_PLSQL,
"It should not be null");
return NameListToString(wdatum->idents);
}
/* convenience routine to copy the name of a T_DATUM */
static char *
CopyNameOfDatum(PLwdatum *wdatum)
{
if (wdatum->ident)
return pstrdup(wdatum->ident);
AssertEreport(wdatum->idents != NIL,
MOD_PLSQL,
"It should not be null");
return NameListToString(wdatum->idents);
}
static void check_record_nest_tableof_index(PLpgSQL_datum* datum)
{
if (datum->dtype == PLPGSQL_DTYPE_RECORD || datum->dtype == PLPGSQL_DTYPE_ROW) {
PLpgSQL_row* row = (PLpgSQL_row*)datum;
for (int i = 0; i < row->nfields; i++) {
PLpgSQL_datum* row_element = NULL;
if (row->ispkg) {
row_element = (PLpgSQL_datum*)(row->pkg->datums[row->varnos[i]]);
} else {
row_element = (PLpgSQL_datum*)(u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[row->varnos[i]]);
}
/* Notice: do not deal record nest record nest table of index, because table of index type can not get */
if (row_element->dtype == PLPGSQL_DTYPE_VAR) {
PLpgSQL_var* var_element = (PLpgSQL_var*)row_element;
if (OidIsValid(var_element->datatype->tableOfIndexType)) {
yyerror("record nested table of index variable do not support entire assign");
}
}
}
}
}
static void
check_assignable(PLpgSQL_datum *datum, int location)
{
PLpgSQL_var *var = NULL;
switch (datum->dtype)
{
case PLPGSQL_DTYPE_VAR:
if (((PLpgSQL_var *) datum)->isconst) {
const char* message = "variable is declared CONSTANT";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
errmsg("\"%s\" is declared CONSTANT",
((PLpgSQL_var *) datum)->refname),
parser_errposition(location)));
}
break;
case PLPGSQL_DTYPE_ROW:
/* always assignable? */
break;
case PLPGSQL_DTYPE_REC:
/* always assignable? What about NEW/OLD? */
break;
case PLPGSQL_DTYPE_RECORD:
break;
case PLPGSQL_DTYPE_RECFIELD:
/* always assignable? */
break;
case PLPGSQL_DTYPE_ARRAYELEM:
var = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[((PLpgSQL_arrayelem *)datum)->arrayparentno];
if (var->isconst) {
const char* message = "array is declared CONSTANT";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate, (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
errmsg("\"%s\" is declared CONSTANT", var->refname),
parser_errposition(location)));
u_sess->plsql_cxt.have_error = true;
}
break;
case PLPGSQL_DTYPE_TABLEELEM:
var = (PLpgSQL_var *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[((PLpgSQL_tableelem *)datum)->tableparentno];
if (var->isconst) {
const char* message = "table is declared CONSTANT";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate, (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
errmsg("\"%s\" is declared CONSTANT", var->refname),
parser_errposition(location)));
u_sess->plsql_cxt.have_error = true;
}
break;
case PLPGSQL_DTYPE_UNKNOWN:
/* package variable? */
break;
default:
elog(errstate, "unrecognized dtype: %d", datum->dtype);
break;
}
}
static Oid get_table_index_type(PLpgSQL_datum* datum, int *tableof_func_dno)
{
PLpgSQL_var* var = (PLpgSQL_var*)datum;
if (OidIsValid(var->datatype->tableOfIndexType) && tableof_func_dno != NULL) {
if (*tableof_func_dno == -1) {
*tableof_func_dno = var->dno;
} else if (*tableof_func_dno != var->dno) {
yyerror("do not support more than 2 table of index by variables call functions in an expr");
}
}
return var->datatype->tableOfIndexType;
}
static void check_bulk_into_type(PLpgSQL_row* row)
{
if (row->nfields == 0) {
char errormsg[128] = {0};
errno_t rc = EOK;
rc = snprintf_s(errormsg, sizeof(errormsg), sizeof(errormsg) - 1,
"bulk collect into target can't be null.");
securec_check_ss(rc, "", "");
yyerror(errormsg);
}
for (int i = 0; i < row->nfields; i++) {
PLpgSQL_var* var = (PLpgSQL_var*)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[row->varnos[i]];
if (var->datatype != NULL && var->datatype->collectionType == PLPGSQL_COLLECTION_TABLE && var->datatype->tableOfIndexType == VARCHAROID) {
char errormsg[128] = {0};
errno_t rc = EOK;
rc = snprintf_s(errormsg, sizeof(errormsg), sizeof(errormsg) - 1,
"index by varchar type %s don't support bulk collect into.", var->refname);
securec_check_ss(rc, "", "");
yyerror(errormsg);
}
}
}
static void check_tableofindex_args(int tableof_var_dno, Oid argtype)
{
if (tableof_var_dno < 0 || u_sess->plsql_cxt.curr_compile_context == NULL) {
return ;
}
PLpgSQL_datum* tableof_var_datum = u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[tableof_var_dno];
if (tableof_var_datum == NULL) {
return ;
} else if (tableof_var_datum->dtype == PLPGSQL_DTYPE_VAR) {
PLpgSQL_var* var = (PLpgSQL_var*)tableof_var_datum;
Oid base_oid = InvalidOid;
Oid indexby_oid = InvalidOid;
if (isTableofType(argtype, &base_oid, &indexby_oid)) {
if (var->datatype->tableOfIndexType != indexby_oid ||
var->datatype->typoid != base_oid) {
yyerror("procedure table of arg types not match");
}
}
} else if (tableof_var_datum->dtype == PLPGSQL_DTYPE_RECORD) {
check_record_nest_tableof_index(tableof_var_datum);
}
}
static void check_table_index(PLpgSQL_datum* datum, char* funcName)
{
PLpgSQL_var* var = (PLpgSQL_var*)datum;
if (var->datatype->tableOfIndexType == VARCHAROID) {
char errormsg[128] = {0};
errno_t rc = EOK;
rc = snprintf_s(errormsg, sizeof(errormsg), sizeof(errormsg) - 1,
"index by varchar type don't support %s function", funcName);
securec_check_ss(rc, "", "");
yyerror(errormsg);
}
}
/*
* check if the table type has index by,
* return real type
*/
static Oid get_table_type(PLpgSQL_datum* datum)
{
PLpgSQL_var* var = (PLpgSQL_var*)datum;
if (OidIsValid(var->datatype->tableOfIndexType)) {
yyerror("multiset don't support index by table of type.");
}
Oid typeOid = var->datatype->typoid;
if (!OidIsValid(typeOid)) {
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("type %s type Oid is invalid.", var->datatype->typname)));
u_sess->plsql_cxt.have_error = true;
}
return typeOid;
}
/* get the attrno of attribute when assign value to arrary element attibute. */
static AttrNumber get_assign_attrno(PLpgSQL_datum* target, char* attrname)
{
int nsubscripts = 0;
Oid parenttypoid;
int32 arraytypmod;
Oid arraytypoid;
Oid elemtypoid;
AttrNumber attrno = -1;
PLpgSQL_arrayelem* arrayelem = NULL;
PLpgSQL_tableelem* tableelem = NULL;
/*
* To handle constructs like x[1][2] := something, we have to
* be prepared to deal with a chain of arrayelem datums. Chase
* back to find the base array datum.
*/
if (target->dtype == PLPGSQL_DTYPE_ARRAYELEM) {
do {
arrayelem = (PLpgSQL_arrayelem*)target;
if (nsubscripts >= MAXDIM) {
const char* message = "number of array dimensions exceeds the maximum allowed in assignment.";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmodule(MOD_PLSQL),
errmsg("number of array dimensions exceeds the maximum allowed in assignment."),
errdetail("number of array dimensions (%d) exceeds the maximum allowed (%d) in assignment. ",
nsubscripts + 1, MAXDIM),
errcause("too large array dimensions"),
erraction("reduce the array dimensions")));
u_sess->plsql_cxt.have_error = true;
}
nsubscripts++;
target = u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[arrayelem->arrayparentno];
} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
} else {
do {
tableelem = (PLpgSQL_tableelem*)target;
if (nsubscripts >= MAXDIM) {
const char* message = "number of array dimensions exceeds the maximum allowed in assignment.";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmodule(MOD_PLSQL),
errmsg("number of array dimensions exceeds the maximum allowed in assignment."),
errdetail("number of array dimensions (%d) exceeds the maximum allowed (%d) in assignment. ",
nsubscripts + 1, MAXDIM),
errcause("too large array dimensions"),
erraction("reduce the array dimensions")));
u_sess->plsql_cxt.have_error = true;
}
nsubscripts++;
target = u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[tableelem->tableparentno];
} while (target->dtype == PLPGSQL_DTYPE_TABLEELEM);
}
/* array element type must be PLPGSQL_DTYPE_VAR, otherwise user referencing array incorrectly. */
if (target->dtype != PLPGSQL_DTYPE_VAR) {
const char* message = "subscripted object in assignment is not an array";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmodule(MOD_PLSQL),
errmsg("subscripted object in assignment is not an array"),
errdetail("subscripted variable in assignment is not an array"),
errcause("incorrectly referencing variables"),
erraction("modify assign variable")));
u_sess->plsql_cxt.have_error = true;
}
/* get the array element typoid */
PLpgSQL_var* var = (PLpgSQL_var*)target;
parenttypoid = var->datatype->typoid;
arraytypmod = var->datatype->atttypmod;
arraytypoid = getBaseTypeAndTypmod(parenttypoid, &arraytypmod);
elemtypoid = get_element_type(arraytypoid);
if (!OidIsValid(elemtypoid)) {
const char* message = "subscripted object in assignment is not an array";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmodule(MOD_PLSQL),
errmsg("subscripted object in assignment is not an array"),
errdetail("subscripted variable \"%s\" in assignment is not an array", var->refname),
errcause("incorrectly referencing variables"),
erraction("modify assign variable")));
u_sess->plsql_cxt.have_error = true;
}
/* get tupledesc by typoid */
TupleDesc elemtupledesc =
lookup_rowtype_tupdesc_noerror(elemtypoid, arraytypmod,true);
if (elemtupledesc == NULL){
const char* message = "array element type is not composite in assignment";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmodule(MOD_PLSQL),
errmsg("array element type is not composite in assignment"),
errdetail("array variable \"%s\" must be composite when assign value to attibute", var->refname),
errcause("incorrectly referencing variables"),
erraction("modify assign variable")));
}
/* search the matched attribute */
for (int i = 0; i < elemtupledesc->natts; i++) {
if (namestrcmp(&(elemtupledesc->attrs[i]->attname), attrname) == 0) {
attrno = i;
break;
}
}
ReleaseTupleDesc(elemtupledesc);
/* attrno = -1 means there is no matched attribute */
if (attrno == -1) {
const char* message = "attribute does not exists";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmodule(MOD_PLSQL),
errmsg("attribute \"%s\" does not exist", attrname),
errdetail("attribute \"%s\" does not exist in array variable \"%s\"", attrname, var->refname),
errcause("incorrectly referencing variables"),
erraction("modify assign variable")));
u_sess->plsql_cxt.have_error = true;
}
return attrno;
}
/*
* Brief : support array variable as select into and using out target.
* Description : if an array element is detected, add it to the u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[]
* and return true, else return false.
* Notes :
*/
static bool
read_into_using_add_arrayelem(char **fieldnames, int *varnos, int *nfields, int tmpdno, int *tok)
{
PLpgSQL_arrayelem *newp = NULL;
char tokExpected[2];
int toktmp = 0;
/* validation of arguments */
if ((NULL == fieldnames) || (NULL == varnos) || (NULL == nfields) || (NULL == tok))
{
const char* message = "pointer is null in read_into_add_arrayelem function!";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg(" pointer is null in read_into_add_arrayelem function! ")));
return false;
}
if (('[' == *tok) || ('(' == *tok))
{
if ('[' == *tok)
toktmp = ']';
else
toktmp = ')';
tokExpected[0] = (char)toktmp;
tokExpected[1] = '\0';
newp = (PLpgSQL_arrayelem *)palloc0(sizeof(PLpgSQL_arrayelem));
newp->arrayparentno = tmpdno;
newp->dtype = PLPGSQL_DTYPE_ARRAYELEM;
/* get the array index expression */
newp->subscript = read_sql_expression(toktmp, &tokExpected[0]);
if(NULL == newp->subscript) {
const char* message = " error near arrary name!";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(" error near arrary name! ")));
u_sess->plsql_cxt.have_error = true;
}
newp->assignattrno = -1;
*tok = yylex();
if (*tok == '.'){
newp->assignattrno = get_assign_attrno((PLpgSQL_datum *)newp, get_attrname(*tok));
*tok = yylex();
if(*tok == '.') {
yyerror("assign value to deep level attribute is not supported in SELECT/FETCH INTO method");
}
}
plpgsql_adddatum((PLpgSQL_datum *)newp);
fieldnames[*nfields] = pstrdup("arrayelem");
varnos[(*nfields)++] = newp->dno;
/* is an array element, return true */
return true;
}
return false;
}
/*
* Brief : support array variable as select into and using out target.
* Description : if an array element is detected, add it to the u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[]
* and return true, else return false.
* Notes :
*/
static bool
read_into_using_add_tableelem(char **fieldnames, int *varnos, int *nfields, int tmpdno, int *tok)
{
PLpgSQL_tableelem *newp = NULL;
char tokExpected[2];
int toktmp = 0;
/* validation of arguments */
if ((NULL == fieldnames) || (NULL == varnos) || (NULL == nfields) || (NULL == tok))
{
const char* message = " pointer is null in read_into_add_arrayelem function!";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg(" pointer is null in read_into_add_arrayelem function! ")));
u_sess->plsql_cxt.have_error = true;
return false;
}
if (('[' == *tok) || ('(' == *tok))
{
if ('[' == *tok)
toktmp = ']';
else
toktmp = ')';
tokExpected[0] = (char)toktmp;
tokExpected[1] = '\0';
newp = (PLpgSQL_tableelem *)palloc0(sizeof(PLpgSQL_tableelem));
newp->tableparentno = tmpdno;
newp->dtype = PLPGSQL_DTYPE_TABLEELEM;
/* get the array index expression */
newp->subscript = read_sql_expression(toktmp, &tokExpected[0]);
if(NULL == newp->subscript) {
const char* message = " error near arrary name!";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(" error near arrary name! ")));
u_sess->plsql_cxt.have_error = true;
}
newp->assignattrno = -1;
*tok = yylex();
if (*tok == '.'){
*tok = yylex();
newp->assignattrno = get_assign_attrno((PLpgSQL_datum *)newp, get_attrname(*tok));
*tok = yylex();
}
plpgsql_adddatum((PLpgSQL_datum *)newp);
fieldnames[*nfields] = pstrdup("arrayelem");
varnos[(*nfields)++] = newp->dno;
/* is an array element, return true */
return true;
}
return false;
}
/*
* Read the argument of an INTO clause. On entry, we have just read the
* INTO keyword.
*/
static void
read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict, bool bulk_collect)
{
int tok;
/* Set default results */
*rec = NULL;
*row = NULL;
if (strict)
*strict = true;
tok = yylex();
if (strict && tok == K_STRICT)
{
*strict = true;
tok = yylex();
} else if (strict && bulk_collect) {
/* bulk into target can be assigned null */
*strict = false;
}
/*
* 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:
case T_VARRAY_VAR:
case T_TABLE_VAR:
case T_PACKAGE_VARIABLE:
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW
|| yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_RECORD)
{
check_assignable(yylval.wdatum.datum, yylloc);
*row = (PLpgSQL_row *) yylval.wdatum.datum;
if ((tok = yylex()) == ',') {
const char* message = "record or row variable cannot be part of multiple-item INTO list";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("record or row variable cannot be part of multiple-item INTO list"),
parser_errposition(yylloc)));
}
plpgsql_push_back_token(tok);
}
else if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
{
check_assignable(yylval.wdatum.datum, yylloc);
*rec = (PLpgSQL_rec *) yylval.wdatum.datum;
if ((tok = yylex()) == ',') {
const char* message = "record or row variable cannot be part of multiple-item INTO list";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("record or row variable cannot be part of multiple-item INTO list"),
parser_errposition(yylloc)));
}
plpgsql_push_back_token(tok);
}
else
{
*row = read_into_array_table_scalar_list(NameOfDatum(&(yylval.wdatum)),
yylval.wdatum.datum, yylval.wdatum.dno, yylloc, bulk_collect);
}
break;
case T_PLACEHOLDER:
*row = read_into_placeholder_scalar_list(yylval.word.ident, yylloc);
break;
default:
/* just to give a better message than "syntax error" */
current_token_is_not_variable(tok);
break;
}
}
/*
* Read the argument of an USING IN / OUT clause.
*/
static void
read_using_target(List **in_expr, PLpgSQL_row **out_row)
{
int tok = 0;
int out_nfields= 0;
int out_varnos[1024] = {0};
char *out_fieldnames[1024] = {0};
bool isin = false;
*in_expr = NULL;
*out_row = NULL;
do
{
tok=yylex();
if(K_IN == tok)
{
tok = yylex();
isin = true;
}
if(K_OUT == tok)
{
char * tempvar = NULL;
int tempdno = 0;
PLpgSQL_expr * tempexpr = NULL;
tok = yylex();
switch(tok)
{
case T_DATUM:
tempvar = pstrdup(NameOfDatum(&(yylval.wdatum)));
tempdno = yylval.wdatum.dno;
plpgsql_push_back_token(tok);
tempexpr =read_sql_construct(',',';',',',", or ;","SELECT ",true,true,true,NULL,&tok);
tempexpr->isouttype = true;
*in_expr=lappend((*in_expr), tempexpr);
if (!read_into_using_add_arrayelem(out_fieldnames, out_varnos, &out_nfields, tempdno, &tok))
{
out_fieldnames[out_nfields] = tempvar;
out_varnos[out_nfields++] = tempdno;
}
else
{
if (isin) {
const char* message = " using can't support array parameter with in out ";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(" using can't support array parameter with in out !")));
}
}
break;
default:
const char* message = "not all the parameters are scalar variables.";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("not all the parameters are scalar variables.")));
}
}
else
{
PLpgSQL_expr * expr = NULL;
plpgsql_push_back_token(tok);
expr = read_sql_construct(',',';',',',", or ;","SELECT ",true,true,true,NULL,&tok);
*in_expr=lappend((*in_expr), expr);
isin = false;
}
}while(tok== ',');
/*
* We read an extra, non-comma token from yylex(), so push it
* back onto the input stream
*/
plpgsql_push_back_token(tok);
if(out_nfields)
{
(*out_row) = (PLpgSQL_row*)palloc0(sizeof(PLpgSQL_row));
(*out_row)->dtype = PLPGSQL_DTYPE_ROW;
(*out_row)->refname = pstrdup("*internal*");
(*out_row)->lineno = plpgsql_location_to_lineno(yylloc);
(*out_row)->rowtupdesc = NULL;
(*out_row)->nfields = out_nfields;
(*out_row)->fieldnames = (char **)palloc(sizeof(char *) * out_nfields);
(*out_row)->varnos = (int *)palloc(sizeof(int) * out_nfields);
(*out_row)->isImplicit = true;
while (--out_nfields >= 0)
{
(*out_row)->fieldnames[out_nfields] = out_fieldnames[out_nfields];
(*out_row)->varnos[out_nfields] = out_varnos[out_nfields];
}
plpgsql_adddatum((PLpgSQL_datum *)(*out_row));
}
}
/*
* 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_dno,
int initial_location)
{
int nfields;
char *fieldnames[1024] = {NULL};
int varnos[1024] = {0};
PLpgSQL_row *row;
int tok;
check_assignable(initial_datum, initial_location);
fieldnames[0] = initial_name;
varnos[0] = initial_dno;
nfields = 1;
while ((tok = yylex()) == ',')
{
/* Check for array overflow */
if (nfields >= 1024) {
const char* message = "too many INTO variables specified.";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many INTO variables specified"),
parser_errposition(yylloc)));
u_sess->plsql_cxt.have_error = true;
break;
}
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) {
const char* message = "variable is not a scalar variable";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
NameOfDatum(&(yylval.wdatum))),
parser_errposition(yylloc)));
u_sess->plsql_cxt.have_error = true;
break;
}
fieldnames[nfields] = NameOfDatum(&(yylval.wdatum));
varnos[nfields++] = yylval.wdatum.dno;
break;
default:
/* just to give a better message than "syntax error" */
current_token_is_not_variable(tok);
break;
}
}
/*
* We read an extra, non-comma token from yylex(), so push it
* back onto the input stream
*/
plpgsql_push_back_token(tok);
row = (PLpgSQL_row *)palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = pstrdup("*internal*");
row->lineno = plpgsql_location_to_lineno(initial_location);
row->rowtupdesc = NULL;
row->nfields = nfields;
row->fieldnames = (char **)palloc(sizeof(char *) * nfields);
row->varnos = (int *)palloc(sizeof(int) * nfields);
row->isImplicit = true;
while (--nfields >= 0)
{
row->fieldnames[nfields] = fieldnames[nfields];
row->varnos[nfields] = varnos[nfields];
}
plpgsql_adddatum((PLpgSQL_datum *)row);
return row;
}
/*
* 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_array_table_scalar_list(char *initial_name,
PLpgSQL_datum *initial_datum,
int initial_dno,
int initial_location,
bool bulk_collect)
{
int nfields = 0;
char *fieldnames[1024] = {NULL};
int varnos[1024] = {0};
PLpgSQL_row *row;
int tok;
int toktmp = 0;
int tmpdno = 0;
int type_flag = -1;
bool isarrayelem = false;
char* nextname = NULL;
check_assignable(initial_datum, initial_location);
tmpdno = initial_dno;
tok = yylex();
get_datum_tok_type(initial_datum, &type_flag);
if (type_flag == PLPGSQL_TOK_TABLE_VAR) {
isarrayelem = read_into_using_add_tableelem(fieldnames, varnos, &nfields, tmpdno, &tok);
} else {
isarrayelem = read_into_using_add_arrayelem(fieldnames, varnos, &nfields, tmpdno, &tok);
}
if (!isarrayelem)
{
fieldnames[0] = initial_name;
varnos[0] = initial_dno;
nfields = 1;
}
while (',' == tok)
{
/* Check for array overflow */
if (nfields >= 1024)
{
const char* message = "too many INTO variables specified";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many INTO variables specified"),
parser_errposition(yylloc)));
return NULL;
}
toktmp = yylex();
type_flag = -1;
switch (toktmp)
{
case T_DATUM:
check_assignable(yylval.wdatum.datum, yylloc);
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) {
const char* message = "it not a scalar variable";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
NameOfDatum(&(yylval.wdatum))),
parser_errposition(yylloc)));
}
tmpdno = yylval.wdatum.dno;
nextname = NameOfDatum(&(yylval.wdatum));
fieldnames[nfields] = nextname;
varnos[nfields++] = tmpdno;
tok = yylex();
break;
case T_PACKAGE_VARIABLE:
check_assignable(yylval.wdatum.datum, yylloc);
if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC) {
const char* message = "it not a scalar variable";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("\"%s\" is not a scalar variable",
NameOfDatum(&(yylval.wdatum))),
parser_errposition(yylloc)));
u_sess->plsql_cxt.have_error = true;
}
tmpdno = yylval.wdatum.dno;
nextname = NameOfDatum(&(yylval.wdatum));
type_flag = -1;
get_datum_tok_type(yylval.wdatum.datum, &type_flag);
if (type_flag == -1) {
fieldnames[nfields] = nextname;
varnos[nfields++] = tmpdno;
tok = yylex();
break;
} else if (type_flag == PLPGSQL_TOK_TABLE_VAR) {
tok = yylex();
if (tok < -1)
return NULL;
if (!read_into_using_add_tableelem(fieldnames, varnos, &nfields, tmpdno, &tok))
{
if (bulk_collect) {
fieldnames[nfields] = nextname;
varnos[nfields++] = tmpdno;
break;
}
const char* message = "error when read table var subscript";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(" error when read table var subscript! ")));
u_sess->plsql_cxt.have_error = true;
return NULL;
}
break;
} else {
tok = yylex();
if (tok < -1)
return NULL;
if (!read_into_using_add_arrayelem(fieldnames, varnos, &nfields, tmpdno, &tok))
{
if (bulk_collect) {
fieldnames[nfields] = nextname;
varnos[nfields++] = tmpdno;
break;
}
const char* message = "error when read array var subscript";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(" error when read array var subscript! ")));
u_sess->plsql_cxt.have_error = true;
return NULL;
}
break;
}
case T_VARRAY_VAR:
check_assignable(yylval.wdatum.datum, yylloc);
tmpdno = yylval.wdatum.dno;
nextname = NameOfDatum(&(yylval.wdatum));
tok = yylex();
if (tok < -1)
return NULL;
if (!read_into_using_add_arrayelem(fieldnames, varnos, &nfields, tmpdno, &tok))
{
if (bulk_collect) {
fieldnames[nfields] = nextname;
varnos[nfields++] = tmpdno;
break;
}
const char* message = "error near arrary name!";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(" error near arrary name! ")));
return NULL;
}
break;
case T_TABLE_VAR:
check_assignable(yylval.wdatum.datum, yylloc);
tmpdno = yylval.wdatum.dno;
nextname = NameOfDatum(&(yylval.wdatum));
tok = yylex();
if (tok < -1)
return NULL;
if (!read_into_using_add_tableelem(fieldnames, varnos, &nfields, tmpdno, &tok))
{
if (bulk_collect) {
fieldnames[nfields] = nextname;
varnos[nfields++] = tmpdno;
break;
}
const char* message = "error near table name";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(" error near table name! ")));
u_sess->plsql_cxt.have_error = true;
return NULL;
}
break;
default:
tok = yylex();
if (tok < -1)
return NULL;
/* just to give a better message than "syntax error" */
current_token_is_not_variable(tok);
break;
}
}
/*
* We read an extra, non-comma token from yylex(), so push it
* back onto the input stream
*/
plpgsql_push_back_token(tok);
row = (PLpgSQL_row*)palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = pstrdup("*internal*");
row->lineno = plpgsql_location_to_lineno(initial_location);
row->rowtupdesc = NULL;
row->nfields = nfields;
row->fieldnames = (char**)palloc(sizeof(char *) * nfields);
row->varnos = (int*)palloc(sizeof(int) * nfields);
row->isImplicit = true;
while (--nfields >= 0)
{
row->fieldnames[nfields] = fieldnames[nfields];
row->varnos[nfields] = varnos[nfields];
}
plpgsql_adddatum((PLpgSQL_datum *)row);
return row;
}
/*
* 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. The function is for placeholders.
*/
static PLpgSQL_row *
read_into_placeholder_scalar_list(char *initial_name,
int initial_location)
{
int intoplaceholders;
PLpgSQL_row *row;
int tok;
intoplaceholders = 1;
while ((tok = yylex()) == ',')
{
/* Check for array overflow */
if ( intoplaceholders >= 1024 ) {
const char* message = "too many INTO placeholders specified";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many INTO placeholders specified"),
parser_errposition(yylloc)));
u_sess->plsql_cxt.have_error = true;
break;
}
tok = yylex();
switch (tok)
{
case T_PLACEHOLDER:
intoplaceholders++;
break;
default:
const char* message = "invalid placeholder specified";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid placeholder specified"),
parser_errposition(yylloc)));
break;
}
}
/*
* We read an extra, non-comma token from yylex(), so push it
* back onto the input stream
*/
plpgsql_push_back_token(tok);
row = (PLpgSQL_row*)palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = pstrdup("*internal*");
row->lineno = plpgsql_location_to_lineno(initial_location);
row->rowtupdesc = NULL;
row->nfields = 0;
row->fieldnames = NULL;
row->varnos = NULL;
row->intoplaceholders = intoplaceholders;
row->intodatums = NULL;
row->isImplicit = true;
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 initial_dno,
int lineno, int location)
{
PLpgSQL_row *row;
check_assignable(initial_datum, location);
row = (PLpgSQL_row *)palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = pstrdup("*internal*");
row->lineno = lineno;
row->rowtupdesc = NULL;
row->nfields = 1;
row->fieldnames = (char **)palloc(sizeof(char *));
row->varnos = (int *)palloc(sizeof(int));
row->fieldnames[0] = initial_name;
row->varnos[0] = initial_dno;
row->isImplicit = true;
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 (!u_sess->plsql_cxt.curr_compile_context->plpgsql_check_syntax)
return;
cbarg.location = location;
cbarg.leaderlen = leaderlen;
syntax_errcontext.callback = plpgsql_sql_error_callback;
syntax_errcontext.arg = &cbarg;
syntax_errcontext.previous = t_thrd.log_cxt.error_context_stack;
t_thrd.log_cxt.error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_tmp_cxt);
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
(void) raw_parser(stmt);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
t_thrd.log_cxt.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 internalerrpos 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)
{
Oid type_id;
int32 typmod;
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
MemoryContext oldCxt = NULL;
cbarg.location = location;
cbarg.leaderlen = 0;
syntax_errcontext.callback = plpgsql_sql_error_callback;
syntax_errcontext.arg = &cbarg;
syntax_errcontext.previous = t_thrd.log_cxt.error_context_stack;
t_thrd.log_cxt.error_context_stack = &syntax_errcontext;
/*
* parseTypeString is only for getting type_id and typemod, who are both scalars,
* so memory used in it is all temp.
*/
oldCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_tmp_cxt);
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
/* Let the main parser try to parse it under standard SQL rules */
parseTypeString(string, &type_id, &typmod);
(void)MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
t_thrd.log_cxt.error_context_stack = syntax_errcontext.previous;
/* Okay, build a PLpgSQL_type data structure for it */
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile == NULL)
{
return plpgsql_build_datatype(type_id, typmod, 0);
}
return plpgsql_build_datatype(type_id, typmod,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_input_collation);
}
/* Build a arrary_type by elem_type. */
static PLpgSQL_type * build_array_type_from_elemtype(PLpgSQL_type *elem_type)
{
PLpgSQL_type *arrary_type;
Oid arraytypoid = get_array_type(elem_type->typoid);
if (arraytypoid == InvalidOid) {
ereport(errstate,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("array type not found"),
errdetail("array type of \"%s\" does not exist.", elem_type->typname),
errcause("undefined object"),
erraction("check typename")));
}
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile == NULL) {
arrary_type = plpgsql_build_datatype(arraytypoid, elem_type->atttypmod, 0);
} else {
arrary_type = plpgsql_build_datatype(arraytypoid, elem_type->atttypmod,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_input_collation);
}
return arrary_type;
}
static PLpgSQL_var* plpgsql_build_nested_variable(PLpgSQL_var *nest_table, bool isconst, char* name, int lineno)
{
PLpgSQL_var *build_nest_table = NULL;
PLpgSQL_type *new_var_type = NULL;
char nestname[NAMEDATALEN] = {0};
errno_t rc = EOK;
rc = snprintf_s(nestname, sizeof(nestname), sizeof(nestname) - 1, "%s_nest", name);
securec_check_ss(rc, "", "");
/* set nested table type */
new_var_type = build_array_type_from_elemtype(nest_table->datatype);
new_var_type->tableOfIndexType = nest_table->datatype->tableOfIndexType;
new_var_type->collectionType = nest_table->datatype->collectionType;
build_nest_table = (PLpgSQL_var *)plpgsql_build_variable(nestname, lineno, new_var_type, true);
build_nest_table->isconst = isconst;
build_nest_table->default_val = NULL;
build_nest_table->nest_layers = nest_table->nest_layers;
if (nest_table->nest_table != NULL) {
build_nest_table->nest_table = plpgsql_build_nested_variable(nest_table->nest_table, isconst, name, lineno);
}
return build_nest_table;
}
static int get_nest_tableof_layer(PLpgSQL_var *var, const char *typname, int errstate)
{
int depth = 0;
while (var != NULL) {
depth++;
var = var->nest_table;
}
if (depth + 1 > MAXDIM) {
ereport(errstate,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmodule(MOD_PLSQL),
errmsg("Layer number of nest tableof type exceeds the maximum allowed."),
errdetail("Define nest tableof type \"%s\" layers (%d) exceeds the maximum allowed (%d).", typname, depth + 1, MAXDIM),
errcause("too many nested layers"),
erraction("check define of table of type")));
u_sess->plsql_cxt.have_error = true;
}
return depth + 1;
}
static void getPkgFuncTypeName(char* typname, char** functypname, char** pkgtypname)
{
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile != NULL) {
*functypname = CastPackageTypeName(typname,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_oid, false);
if (strlen(*functypname) >= NAMEDATALEN) {
const char* message = "record name too long";
InsertErrorMessage(message, plpgsql_yylloc, true);
ereport(ERROR,
(errcode(ERRCODE_NAME_TOO_LONG),
errmsg("type name too long"),
errdetail("record name %s with func oid %d should be less the %d letters.",
typname, u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_oid, NAMEDATALEN)));
}
}
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package != NULL) {
*pkgtypname = CastPackageTypeName(typname,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid, true);
if (strlen(*pkgtypname) >= NAMEDATALEN) {
const char* message = "record name too long";
InsertErrorMessage(message, plpgsql_yylloc, true);
ereport(ERROR,
(errcode(ERRCODE_NAME_TOO_LONG),
errmsg("type name too long"),
errdetail("record name %s with package name %s should be less the %d letters.",
typname, u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_signature, NAMEDATALEN)));
}
}
}
/*
* look up composite type from namespace
*/
static PLpgSQL_nsitem* lookupCompositeType(char* functypname, char* pkgtypname)
{
PLpgSQL_nsitem* ns = NULL;
/* if compile func, search func namesapce first */
if (functypname != NULL) {
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, functypname, NULL, NULL, NULL);
if (ns != NULL || pkgtypname == NULL) {
return ns;
}
}
/* next search package namespace */
PLpgSQL_package* pkg = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package;
ns = plpgsql_ns_lookup(pkg->public_ns, false, pkgtypname, NULL, NULL, NULL);
if (ns != NULL) {
return ns;
}
/* may compile pkg spec now, search top again */
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, pkgtypname, NULL, NULL, NULL);
if (ns != NULL) {
return ns;
}
/* if compile package body, search private namespace */
if (!pkg->is_spec_compiling) {
/* private type should cast '$' */
StringInfoData castTypName;
initStringInfo(&castTypName);
appendStringInfoString(&castTypName, "$");
appendStringInfoString(&castTypName, pkgtypname);
/* search private namespace, when complie, it is the top */
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, castTypName.data, NULL, NULL, NULL);
pfree_ext(castTypName.data);
}
return ns;
}
static Oid getOldTypeOidByTypeName(char** typname, char** schamaName, char* functypname, char* pkgtypname, Oid* pkgoid)
{
Oid nameSpaceOid = InvalidOid;
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile != NULL) {
*pkgoid = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_oid;
*typname = functypname;
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_searchpath != NULL) {
nameSpaceOid = linitial_oid(u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_searchpath->schemas);
}
} else {
*pkgoid = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid;
*typname = pkgtypname;
nameSpaceOid = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->namespaceOid;
}
if (OidIsValid(nameSpaceOid)) {
*schamaName = get_namespace_name(nameSpaceOid);
} else {
nameSpaceOid = getCurrentNamespace();
}
Oid oldtypeoid = GetSysCacheOid2(TYPENAMENSP, PointerGetDatum(*typname),
ObjectIdGetDatum(nameSpaceOid));
return oldtypeoid;
}
static RangeVar* createRangeVarForCompositeType(char* schamaName, char* typname)
{
RangeVar *r = makeNode(RangeVar);
r->catalogname = NULL;
r->schemaname = schamaName;
r->relname = pstrdup(typname);
r->relpersistence = RELPERSISTENCE_PERMANENT;
r->location = -1;
r->ispartition = false;
r->isbucket = false;
r->buckets = NIL;
return r;
}
static void buildDependencyForCompositeType(Oid newtypeoid)
{
ObjectAddress myself, referenced;
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile != NULL) {
myself.classId = ProcedureRelationId;
myself.objectId = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->fn_oid;
myself.objectSubId = 0;
} else {
myself.classId = PackageRelationId;
myself.objectId = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid;
myself.objectSubId = 0;
}
referenced.classId = TypeRelationId;
referenced.objectId = newtypeoid;
referenced.objectSubId = 0;
recordDependencyOn(&referenced, &myself, DEPENDENCY_AUTO);
CommandCounterIncrement();
}
static bool checkDuplicateAttrName(TupleDesc tupleDesc)
{
int attrnum = tupleDesc->natts;
for (int i = 0; i < attrnum; i++) {
Form_pg_attribute attr1 = tupleDesc->attrs[i];
for (int j = i + 1; j < attrnum; j++) {
Form_pg_attribute attr2 = tupleDesc->attrs[j];
if (strcmp(NameStr(attr1->attname), NameStr(attr2->attname)) == 0) {
return true;
}
}
}
return false;
}
#ifndef ENABLE_MULTIPLE_NODES
static bool checkAllAttrName(TupleDesc tupleDesc)
{
int attrnum = tupleDesc->natts;
for (int i = 0; i < attrnum; i++) {
Form_pg_attribute pg_att_form = tupleDesc->attrs[i];
char* att_name = NameStr(pg_att_form->attname);
if (strcmp(att_name, "?column?") != 0) {
return false;
}
}
return true;
}
#endif
static Oid createCompositeTypeForCursor(PLpgSQL_var* var, PLpgSQL_expr* expr)
{
#ifdef ENABLE_MULTIPLE_NODES
return InvalidOid;
#endif
TupleDesc tupleDesc = getCursorTupleDesc(expr, false);
if (tupleDesc == NULL || tupleDesc->natts == 0) {
return InvalidOid;
}
/*
* if cursor is select a,a from t1, cannot have same attr name.
*/
bool isHaveDupName = checkDuplicateAttrName(tupleDesc);
if (isHaveDupName) {
return InvalidOid;
}
/* concatenate name string with function name for composite type, which to avoid conflict. */
char* functypname = NULL;
char* pkgtypname = NULL;
char* typname = NULL;
char* schamaname = NULL;
Oid pkgoid = InvalidOid;
getPkgFuncTypeName(var->refname, &functypname, &pkgtypname);
Oid oldtypeoid = getOldTypeOidByTypeName(&typname, &schamaname, functypname, pkgtypname, &pkgoid);
Oid newtypeoid = InvalidOid;
if (OidIsValid(oldtypeoid)) {
/* already build one, just use it */
newtypeoid = oldtypeoid;
} else {
RangeVar* r = createRangeVarForCompositeType(schamaname, typname);
List* codeflist = NULL;
int attrnum = tupleDesc->natts;
for (int i = 0; i < attrnum; i++) {
ColumnDef *n = makeNode(ColumnDef);
Form_pg_attribute attr = tupleDesc->attrs[i];
n->colname = pstrdup(NameStr(attr->attname));
n->typname = makeTypeNameFromOid(attr->atttypid, attr->atttypmod);
n->inhcount = 0;
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
n->collClause = NULL;
n->clientLogicColumnRef=NULL;
n->collOid = InvalidOid;
n->constraints = NIL;
codeflist = lappend(codeflist, (Node*)n);
}
DefineCompositeType(r, codeflist);
newtypeoid = GetSysCacheOid2(TYPENAMENSP, PointerGetDatum(typname),
ObjectIdGetDatum(getCurrentNamespace()));
pfree_ext(r);
ListCell* cell = NULL;
foreach (cell, codeflist) {
ColumnDef* n = (ColumnDef*)lfirst(cell);
pfree_ext(n->colname);
}
list_free_deep(codeflist);
/* build dependency on created composite type. */
buildDependencyForCompositeType(newtypeoid);
}
pfree_ext(functypname);
pfree_ext(pkgtypname);
return newtypeoid;
}
#ifndef ENABLE_MULTIPLE_NODES
static PLpgSQL_type* build_type_from_cursor_var(PLpgSQL_var* var)
{
PLpgSQL_type *newp = NULL;
Oid typeOid = var->datatype->cursorCompositeOid;
/* build datatype of the created composite type. */
newp = plpgsql_build_datatype(typeOid, -1, InvalidOid);
newp->dtype = PLPGSQL_DTYPE_COMPOSITE;
newp->ttype = PLPGSQL_TTYPE_ROW;
/* add the composite type to datum and namespace. */
int varno = plpgsql_adddatum((PLpgSQL_datum*)newp);
plpgsql_ns_additem(PLPGSQL_NSTYPE_COMPOSITE, varno, var->datatype->typname );
return newp;
}
#endif
/*
* Build a composite type by execute SQL, when record type is nested.
*/
static PLpgSQL_type* build_type_from_record_var(int dno)
{
PLpgSQL_type *newp = NULL;
PLpgSQL_rec_type * var_type = (PLpgSQL_rec_type *)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[dno];
/* concatenate name string with function name for composite type, which to avoid conflict. */
char* functypname = NULL;
char* pkgtypname = NULL;
getPkgFuncTypeName(var_type->typname, &functypname, &pkgtypname);
/* look up composite type from namespace, if exists, we have already build one. */
PLpgSQL_nsitem* ns = lookupCompositeType(functypname, pkgtypname);
if (ns == NULL) {
/* we need to build a composite type, and drop any remaining types. */
Oid newtypeoid = InvalidOid;
char* typname;
char* schamaname = NULL;
Oid pkgoid = InvalidOid;
Oid oldtypeoid = getOldTypeOidByTypeName(&typname, &schamaname, functypname, pkgtypname, &pkgoid);
if (OidIsValid(oldtypeoid)) {
/* already build one, just use it */
if(IsPackageDependType(oldtypeoid, pkgoid)) {
newtypeoid = oldtypeoid;
} else {
ereport(errstate,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("duplicate type name"),
errdetail("type name of \"%s\" duplicated with an existed type when build package or function type.", typname),
errcause("duplicate type name"),
erraction("modify type name")));
}
} else {
RangeVar* r = createRangeVarForCompositeType(schamaname, typname);
List* codeflist = NULL;
int attrnum = var_type->attrnum;
for (int i = 0; i < attrnum; i++) {
ColumnDef *n = makeNode(ColumnDef);
n->colname = pstrdup(var_type->attrnames[i]);
n->typname = make_typename_from_datatype(var_type->types[i]);
n->inhcount = 0;
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
n->collClause = NULL;
n->clientLogicColumnRef=NULL;
n->collOid = InvalidOid;
n->constraints = NIL;
codeflist = lappend(codeflist, (Node*)n);
}
DefineCompositeType(r, codeflist);
newtypeoid = GetSysCacheOid2(TYPENAMENSP, PointerGetDatum(typname),
ObjectIdGetDatum(getCurrentNamespace()));
pfree_ext(r);
list_free_deep(codeflist);
/* build dependency on created composite type. */
buildDependencyForCompositeType(newtypeoid);
}
/* build datatype of the created composite type. */
newp = plpgsql_build_datatype(newtypeoid, -1, InvalidOid);
newp->dtype = PLPGSQL_DTYPE_COMPOSITE;
newp->ttype = PLPGSQL_TTYPE_ROW;
/* add the composite type to datum and namespace. */
int varno = plpgsql_adddatum((PLpgSQL_datum*)newp);
plpgsql_ns_additem(PLPGSQL_NSTYPE_COMPOSITE, varno, typname);
} else {
/* we have already build one, just take it from datums. */
if (ns->itemtype == PLPGSQL_NSTYPE_COMPOSITE) {
newp = (PLpgSQL_type*)(u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[ns->itemno]);
/* build a new one, avoid conflict by array or table of */
newp = plpgsql_build_datatype(newp->typoid, -1, InvalidOid);
newp->dtype = PLPGSQL_DTYPE_COMPOSITE;
newp->ttype = PLPGSQL_TTYPE_ROW;
} else {
ereport(errstate,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("duplicate declaration"),
errdetail("record %s is duplicate with other objects.", var_type->typname)));
u_sess->plsql_cxt.have_error = true;
}
}
pfree_ext(functypname);
pfree_ext(pkgtypname);
return newp;
}
static Oid plpgsql_build_package_record_type(const char* typname, List* list, bool add2namespace)
{
Oid oldtypeoid = InvalidOid;
Oid newtypeoid = InvalidOid;
char* schamaName = NULL;
Oid pkgNamespaceOid = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->namespaceOid;
if (OidIsValid(pkgNamespaceOid)) {
schamaName = get_namespace_name(pkgNamespaceOid);
} else {
pkgNamespaceOid = getCurrentNamespace();
}
char* casttypename = CastPackageTypeName(typname,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid, true,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->is_spec_compiling);
if (strlen(casttypename) >= NAMEDATALEN ) {
ereport(errstate,
(errcode(ERRCODE_NAME_TOO_LONG),
errmsg("type name too long"),
errdetail("record name %s with package name %s should be less the %d letters.",
typname,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_signature,
NAMEDATALEN)));
u_sess->plsql_cxt.have_error = true;
pfree_ext(casttypename);
}
oldtypeoid = GetSysCacheOid2(TYPENAMENSP, PointerGetDatum(casttypename), ObjectIdGetDatum(pkgNamespaceOid));
if (OidIsValid(oldtypeoid)) {
/* already build on, just use it */
if(IsPackageDependType(oldtypeoid, u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid)) {
newtypeoid = oldtypeoid;
} else {
ereport(errstate,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("duplicate type name"),
errdetail("type name of \"%s\" duplicated with an existed type when build package type.", casttypename),
errcause("duplicate type name"),
erraction("modify package type name")));
}
} else {
RangeVar *r = makeNode(RangeVar);
r->catalogname = NULL;
r->schemaname = schamaName;
r->relname = pstrdup(casttypename);
r->relpersistence = RELPERSISTENCE_PERMANENT;
r->location = -1;
r->ispartition = false;
r->isbucket = false;
r->buckets = NIL;
List* codeflist = NULL;
ListCell* cell = NULL;
PLpgSQL_rec_attr* attr = NULL;
foreach (cell, list) {
attr = (PLpgSQL_rec_attr*)lfirst(cell);
codeflist = lappend(codeflist, make_columnDef_from_attr(attr));
}
DefineCompositeType(r, codeflist);
newtypeoid = GetSysCacheOid2(TYPENAMENSP, PointerGetDatum(casttypename),
ObjectIdGetDatum(getCurrentNamespace()));
/* build dependency on created composite type. */
ObjectAddress myself, referenced;
myself.classId = PackageRelationId;
myself.objectId = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid;
myself.objectSubId = 0;
referenced.classId = TypeRelationId;
referenced.objectId = newtypeoid;
referenced.objectSubId = 0;
recordDependencyOn(&referenced, &myself, DEPENDENCY_AUTO);
CommandCounterIncrement();
pfree_ext(r);
list_free_deep(codeflist);
}
PLpgSQL_type *newtype = NULL;
newtype = plpgsql_build_datatype(newtypeoid, -1, InvalidOid);
newtype->dtype = PLPGSQL_DTYPE_COMPOSITE;
newtype->ttype = PLPGSQL_TTYPE_ROW;
int varno = plpgsql_adddatum((PLpgSQL_datum*)newtype);
plpgsql_ns_additem(PLPGSQL_NSTYPE_COMPOSITE, varno, casttypename);
pfree_ext(casttypename);
return newtypeoid;
}
static void plpgsql_build_package_array_type(const char* typname,Oid elemtypoid, char arraytype)
{
char typtyp;
char* casttypename = CastPackageTypeName(typname, u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid, true,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->is_spec_compiling);
if (strlen(casttypename) >= NAMEDATALEN ) {
ereport(errstate,
(errcode(ERRCODE_NAME_TOO_LONG),
errmsg("type name too long"),
errdetail("array or nested table type name %s with package name %s should be less the %d letters.",
typname, u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_signature, NAMEDATALEN)));
u_sess->plsql_cxt.have_error = true;
}
Oid pkgNamespaceOid = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->namespaceOid;
if (!OidIsValid(pkgNamespaceOid)) {
pkgNamespaceOid = getCurrentNamespace();
}
Oid oldtypeoid = GetSysCacheOid2(TYPENAMENSP, PointerGetDatum(casttypename),
ObjectIdGetDatum(pkgNamespaceOid));
if (OidIsValid(oldtypeoid)) {
/* alread build one, just return */
if(IsPackageDependType(oldtypeoid, u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid)) {
return;
} else {
ereport(errstate,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("duplicate type name"),
errdetail("type name of \"%s\" duplicated with an existed type when build package type.", casttypename),
errcause("duplicate type name"),
erraction("modify package type name")));
}
}
if (arraytype == TYPCATEGORY_TABLEOF ||
arraytype == TYPCATEGORY_TABLEOF_VARCHAR ||
arraytype == TYPCATEGORY_TABLEOF_INTEGER) {
elemtypoid = get_array_type(elemtypoid);
typtyp = TYPTYPE_TABLEOF;
} else {
typtyp = TYPTYPE_BASE;
}
Oid ownerId = InvalidOid;
ownerId = GetUserIdFromNspId(pkgNamespaceOid);
if (!OidIsValid(ownerId)) {
ownerId = GetUserId();
}
Oid typoid = TypeCreate(InvalidOid, /* force the type's OID to this */
casttypename, /* Array type name */
pkgNamespaceOid, /* Same namespace as parent */
InvalidOid, /* Not composite, no relationOid */
0, /* relkind, also N/A here */
ownerId, /* owner's ID */
-1, /* Internal size (varlena) */
typtyp, /* Not composite - typelem is */
arraytype, /* type-category (array or table of) */
false, /* array types are never preferred */
DEFAULT_TYPDELIM, /* default array delimiter */
F_ARRAY_IN, /* array input proc */
F_ARRAY_OUT, /* array output proc */
F_ARRAY_RECV, /* array recv (bin) proc */
F_ARRAY_SEND, /* array send (bin) proc */
InvalidOid, /* typmodin procedure - none */
InvalidOid, /* typmodout procedure - none */
F_ARRAY_TYPANALYZE, /* array analyze procedure */
elemtypoid, /* array element type - the rowtype */
false, /* yes, this is an array type */
InvalidOid, /* this has no array type */
InvalidOid, /* domain base type - irrelevant */
NULL, /* default value - none */
NULL, /* default binary representation */
false, /* passed by reference */
'd', /* alignment - must be the largest! */
'x', /* fully TOASTable */
-1, /* typmod */
0, /* array dimensions for typBaseType */
false, /* Type NOT NULL */
get_typcollation(elemtypoid));
CommandCounterIncrement();
/* build dependency on created composite type. */
ObjectAddress myself, referenced;
myself.classId = PackageRelationId;
myself.objectId = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid;
myself.objectSubId = 0;
referenced.classId = TypeRelationId;
referenced.objectId = typoid;
referenced.objectSubId = 0;
recordDependencyOn(&referenced, &myself, DEPENDENCY_AUTO);
CommandCounterIncrement();
pfree_ext(casttypename);
}
static void plpgsql_build_package_refcursor_type(const char* typname)
{
CreateSynonymStmt stmt;
stmt.replace = true;
Node* lc = NULL;
List* synList = NULL;
List* objList = NULL;
char* casttypename = CastPackageTypeName(typname, u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid, true,
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->is_spec_compiling);
if (strlen(casttypename) >= NAMEDATALEN ) {
ereport(errstate,
(errcode(ERRCODE_NAME_TOO_LONG),
errmsg("type name too long"),
errdetail("cursor type name %s with package name %s should be less the %d letters.",
typname, u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_signature, NAMEDATALEN)));
u_sess->plsql_cxt.have_error = true;
}
Oid pkgNamespaceOid = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->namespaceOid;
if (!OidIsValid(pkgNamespaceOid)) {
pkgNamespaceOid = getCurrentNamespace();
}
HeapTuple tuple = NULL;
tuple = SearchSysCache2(SYNONYMNAMENSP, PointerGetDatum(casttypename), ObjectIdGetDatum(pkgNamespaceOid));
if (HeapTupleIsValid(tuple)) {
Oid synOid = HeapTupleGetOid(tuple);
if(IsPackageDependType(synOid, u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid, true)) {
ReleaseSysCache(tuple);
return;
} else {
ReleaseSysCache(tuple);
ereport(errstate,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("duplicate type name"),
errdetail("type name of \"%s\" duplicated with an existed type when build package type.", casttypename),
errcause("duplicate type name"),
erraction("modify package type name")));
}
}
char* pkgSchemaName = NULL;
if (OidIsValid(pkgNamespaceOid)) {
pkgSchemaName = get_namespace_name(pkgNamespaceOid);
lc = (Node*)makeString(pkgSchemaName);
synList = lappend(synList, lc);
}
lc = (Node*)makeString(casttypename);
synList = lappend(synList, lc);
lc = (Node*)makeString("pg_catalog");
objList = lappend(objList, lc);
lc = (Node*)makeString("refcursor");
objList = lappend(objList, lc);
stmt.synName = synList;
stmt.objName = objList;
CreateSynonym(&stmt);
CommandCounterIncrement();
tuple = NULL;
tuple = SearchSysCache2(SYNONYMNAMENSP, PointerGetDatum(casttypename), ObjectIdGetDatum(pkgNamespaceOid));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("synonym \"%s\" does not exist", casttypename)));
}
Oid synOid = HeapTupleGetOid(tuple);
ReleaseSysCache(tuple);
/* build dependency on Synonym. */
ObjectAddress myself, referenced;
myself.classId = PackageRelationId;
myself.objectId = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid;
myself.objectSubId = 0;
referenced.classId = PgSynonymRelationId;
referenced.objectId = synOid;
referenced.objectSubId = 0;
recordDependencyOn(&referenced, &myself, DEPENDENCY_AUTO);
CommandCounterIncrement();
pfree_ext(casttypename);
pfree_ext(pkgSchemaName);
}
static Node* make_columnDef_from_attr(PLpgSQL_rec_attr* attr)
{
ColumnDef *n = makeNode(ColumnDef);
n->colname = pstrdup(attr->attrname);
n->typname = make_typename_from_datatype(attr->type);
n->inhcount = 0;
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
n->collClause = NULL;
n->clientLogicColumnRef=NULL;
n->collOid = InvalidOid;
n->constraints = NIL;
return (Node*)n;
}
static TypeName* make_typename_from_datatype(PLpgSQL_type* datatype)
{
return makeTypeNameFromOid(datatype->typoid, datatype->atttypmod);
}
/*
* 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) {
const char* message = "end label specified for unlabelled block";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("end label \"%s\" specified for unlabelled block",
end_label),
parser_errposition(end_location)));
}
if (start_label == NULL || end_label == NULL) {
if (errstate == NOTICE) {
u_sess->plsql_cxt.have_error = true;
return;
} else {
const char* message = "end label specified for unlabelled block";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("end label \"%s\" differs from block's label \"%s\"",
end_label, start_label),
parser_errposition(end_location)));
}
}
if (strcmp(start_label, end_label) != 0) {
const char* message = "end label differs from block's label";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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 == '(') {
const char* message = "cursor has no arguments";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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 != '(') {
const char* message = "cursor has arguments";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cursor \"%s\" has arguments",
cursor->refname),
parser_errposition(yylloc)));
}
bool isPkgCur = cursor->ispkg &&
(u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package == NULL
|| u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->pkg_oid != cursor->pkg->pkg_oid);
if (isPkgCur) {
ereport(errstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("package cursor with arguments is only supported to be opened in the same package."),
errdetail("cursor \"%s.%s\" is only supported to be opened in the package \"%s\"",
cursor->pkg->pkg_signature,
cursor->varname == NULL ? cursor->refname : cursor->varname,
cursor->pkg->pkg_signature),
errcause("feature not supported"),
erraction("define this cursor without arguments or open this cursor in same package"),
parser_errposition(yylloc)));
}
/*
* Read the arguments, one by one.
*/
if (cursor->ispkg) {
row = (PLpgSQL_row *) cursor->pkg->datums[cursor->cursor_explicit_argrow];
} else {
row = (PLpgSQL_row *) u_sess->plsql_cxt.curr_compile_context->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 = u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup;
u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
yylex();
argname = yylval.str;
u_sess->plsql_cxt.curr_compile_context->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) {
const char* message = "cursor has no argument named";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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) {
const char* message = "value for parameter of cursor specified more than once";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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)) {
const char* message = "not enough arguments for cursor";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("not enough arguments for cursor \"%s\"",
cursor->refname),
parser_errposition(yylloc)));
}
if (endtoken == ',' && (argc == row->nfields - 1)) {
const char* message = "too many arguments for cursor";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(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++)
{
AssertEreport(argv[argc] != NULL,
MOD_PLSQL,
"It should not be 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 = (PLpgSQL_expr*)palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->ns = plpgsql_ns_top();
expr->idx = (uint32)-1;
expr->out_param_dno = -1;
pfree_ext(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
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;
}
/*
* 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 *newp;
newp = (PLpgSQL_stmt_case *)palloc(sizeof(PLpgSQL_stmt_case));
newp->cmd_type = PLPGSQL_STMT_CASE;
newp->lineno = plpgsql_location_to_lineno(location);
newp->t_expr = t_expr;
newp->t_varno = 0;
newp->case_when_list = case_when_list;
newp->have_else = (else_stmts != NIL);
newp->sqlString = plpgsql_get_curline_query();
/* Get rid of list-with-NULL hack */
if (list_length(else_stmts) == 1 && linitial(else_stmts) == NULL)
newp->else_stmts = NIL;
else
newp->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__",
u_sess->plsql_cxt.curr_compile_context->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, newp->lineno,
plpgsql_build_datatype(INT4OID,
-1,
InvalidOid),
true, true);
newp->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) */
AssertEreport(strncmp(expr->query, "SELECT ", 7) == 0,
MOD_PLSQL,
"copy expression query without SELECT keyword");
/* And do the string hacking */
initStringInfo(&ds);
appendStringInfo(&ds, "SELECT \"%s\" IN (%s)",
varname, expr->query + 7);
pfree_ext(expr->query);
expr->query = pstrdup(ds.data);
/* Adjust expr's namespace to include the case variable */
expr->ns = plpgsql_ns_top();
pfree_ext(ds.data);
}
}
return (PLpgSQL_stmt *) newp;
}
static PLpgSQL_stmt *
make_callfunc_stmt_no_arg(const char *sqlstart, int location, bool withsemicolon, List* funcNameList)
{
char *cp[3] = {0};
HeapTuple proctup = NULL;
Oid *p_argtypes = NULL;
char **p_argnames = NULL;
char *p_argmodes = NULL;
int narg = 0;
List *funcname = NIL;
PLpgSQL_expr* expr = NULL;
FuncCandidateList clist = NULL;
StringInfoData func_inparas;
char *quoted_sqlstart = NULL;
MemoryContext oldCxt = NULL;
/*
* the function make_callfunc_stmt_no_arg is only to assemble a sql statement,
* so the context is set to tmp context.
*/
oldCxt = MemoryContextSwitchTo(u_sess->plsql_cxt.curr_compile_context->compile_tmp_cxt);
/* get the function's name. */
if (funcNameList == NULL) {
plpgsql_parser_funcname(sqlstart, cp, 3);
} else {
funcname = funcNameList;
}
if (funcNameList == NULL) {
if (cp[2] != NULL && cp[2][0] != '\0')
funcname = list_make3(makeString(cp[0]), makeString(cp[1]), makeString(cp[2]));
else if (cp[1] && cp[1][0] != '\0')
funcname = list_make2(makeString(cp[0]), makeString(cp[1]));
else
funcname = list_make1(makeString(cp[0]));
}
/* search the function */
clist = FuncnameGetCandidates(funcname, -1, NIL, false, false, false);
if (clist == NULL)
{
const char* message = "function doesn't exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function \"%s\" doesn't exist ", sqlstart)));
}
proctup = SearchSysCache(PROCOID,
ObjectIdGetDatum(clist->oid),
0, 0, 0);
/* function may be deleted after clist be searched. */
if (!HeapTupleIsValid(proctup))
{
const char* message = "function doesn't exist";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function \"%s\" doesn't exist ", sqlstart)));
}
/* get the all args informations, only "in" parameters if p_argmodes is null */
narg = get_func_arg_info(proctup,&p_argtypes, &p_argnames, &p_argmodes);
int default_args = ((Form_pg_proc)GETSTRUCT(proctup))->pronargdefaults;
ReleaseSysCache(proctup);
// For function with default values, SQL will handle the arg with default value.
if (narg != 0 && narg > default_args) {
const char* message = "function has no enough parameters";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function %s has no enough parameters", sqlstart)));
}
initStringInfo(&func_inparas);
appendStringInfoString(&func_inparas, "CALL ");
quoted_sqlstart = NameListToQuotedString(funcname);
appendStringInfoString(&func_inparas, quoted_sqlstart);
pfree_ext(quoted_sqlstart);
appendStringInfoString(&func_inparas, "(");
appendStringInfoString(&func_inparas, ")");
/* read the end token */
if (!withsemicolon) {
yylex();
}
(void)MemoryContextSwitchTo(oldCxt);
/* generate the expression */
expr = (PLpgSQL_expr*)palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(func_inparas.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->ns = plpgsql_ns_top();
expr->idx = (uint32)-1;
expr->out_param_dno = -1;
PLpgSQL_stmt_perform *perform = NULL;
perform = (PLpgSQL_stmt_perform*)palloc0(sizeof(PLpgSQL_stmt_perform));
perform->cmd_type = PLPGSQL_STMT_PERFORM;
perform->lineno = plpgsql_location_to_lineno(location);
perform->expr = expr;
perform->sqlString = plpgsql_get_curline_query();
return (PLpgSQL_stmt *)perform;
}
/*
* Brief : special handling of dbms_lob.open and dbms_lob.close
* Description : open and close are keyword, which need to be handled separately
* Notes :
*/
static PLpgSQL_stmt *
parse_lob_open_close(int location)
{
StringInfoData func;
int tok = yylex();
char *mode = NULL;
bool is_open = false;
PLpgSQL_expr *expr = NULL;
PLpgSQL_stmt_perform *perform = NULL;
initStringInfo(&func);
appendStringInfoString(&func, "CALL DBMS_LOB.");
tok = yylex();
if (K_OPEN == tok)
{
is_open = true;
appendStringInfoString(&func,"OPEN(");
}
else
appendStringInfoString(&func,"CLOSE(");
if ('(' == (tok = yylex()))
{
tok = yylex();
if (T_DATUM == tok)
appendStringInfoString(&func, NameOfDatum(&yylval.wdatum));
else if (T_PLACEHOLDER == tok)
appendStringInfoString(&func, yylval.word.ident);
else
yyerror("syntax error");
if (is_open)
{
if (',' == (tok = yylex()) && T_CWORD == (tok = yylex()))
{
mode = NameListToString(yylval.cword.idents);
if (strcasecmp(mode, "DBMS_LOB.LOB_READWRITE") != 0
&& strcasecmp(mode, "DBMS_LOB.LOB_READWRITE") != 0)
yyerror("syntax error");
else if (!(')' == yylex() && ';' == yylex()))
yyerror("syntax error");
appendStringInfoChar(&func, ')');
}
else
yyerror("syntax error");
}
else if(')' == yylex() && ';' == yylex())
appendStringInfoChar(&func, ')');
else
yyerror("syntax error");
expr = (PLpgSQL_expr *)palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(func.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->ns = plpgsql_ns_top();
expr->idx = (uint32)-1;
expr->out_param_dno = -1;
perform = (PLpgSQL_stmt_perform*)palloc0(sizeof(PLpgSQL_stmt_perform));
perform->cmd_type = PLPGSQL_STMT_PERFORM;
perform->lineno = plpgsql_location_to_lineno(location);
perform->expr = expr;
perform->sqlString = plpgsql_get_curline_query();
return (PLpgSQL_stmt *)perform;
}
yyerror("syntax error");
return NULL;
}
static void raw_parse_package_function(char* proc_str)
{
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package != NULL)
{
List* raw_parsetree_list = NULL;
u_sess->plsql_cxt.plpgsql_yylloc = plpgsql_yylloc;
u_sess->plsql_cxt.rawParsePackageFunction = true;
raw_parsetree_list = raw_parser(proc_str);
CreateFunctionStmt* stmt;
u_sess->plsql_cxt.rawParsePackageFunction = false;
int rc = 0;
if (raw_parsetree_list == NULL) {
return;
}
stmt = (CreateFunctionStmt *)linitial(raw_parsetree_list);
stmt->queryStr = proc_str;
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->is_spec_compiling) {
stmt->isPrivate = false;
} else {
stmt->isPrivate = true;
}
if (u_sess->parser_cxt.isFunctionDeclare) {
stmt->isFunctionDeclare = true;
} else {
stmt->isFunctionDeclare = false;
}
rc = CompileWhich();
if (rc == PLPGSQL_COMPILE_PACKAGE) {
stmt->startLineNumber = u_sess->plsql_cxt.procedure_start_line;
stmt->firstLineNumber = u_sess->plsql_cxt.procedure_first_line;
}
/* check function name */
CheckDuplicateFunctionName(stmt->funcname);
List *proc_list = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->proc_list;
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package->proc_list = lappend(proc_list,stmt);
} else {
yyerror("kerword PROCEDURE only use in package compile");
}
}
static void CheckDuplicateFunctionName(List* funcNameList)
{
char* schemaname = NULL;
char* pkgname = NULL;
char* funcname = NULL;
DeconstructQualifiedName(funcNameList, &schemaname, &funcname, &pkgname);
if (plpgsql_ns_lookup(plpgsql_ns_top(), true, funcname, NULL, NULL, NULL) != NULL)
yyerror("duplicate declaration");
}
static void IsInPublicNamespace(char* varname) {
if (u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile == NULL &&
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package != NULL) {
PLpgSQL_package* pkg = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package;
if (plpgsql_ns_lookup(pkg->public_ns, true, varname, NULL, NULL, NULL) != NULL) {
const char* message = "duplicate declaration";
InsertErrorMessage(message, plpgsql_yylloc);
ereport(errstate,
(errmodule(MOD_PLSQL), errcode(ERRCODE_SYNTAX_ERROR),
errmsg("duplicate declaration"),
errdetail("name \"%s\" already defined", varname),
errcause("name maybe defined in public variable or type"),
erraction("rename variable or type")));
}
}
}
static void AddNamespaceIfNeed(int dno, char* ident)
{
if (getCompileStatus() != COMPILIE_PKG_FUNC) {
return;
}
if (ident == NULL) {
yyerror("null string when add package type to procedure namespace");
}
if (plpgsql_ns_lookup(plpgsql_ns_top(), true, ident, NULL, NULL, NULL) != NULL) {
return;
}
if (dno == -1) {
plpgsql_ns_additem(PLPGSQL_NSTYPE_REFCURSOR, 0, ident);
return;
}
PLpgSQL_datum* datum = u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[dno];
switch(datum->dtype) {
case PLPGSQL_DTYPE_RECORD_TYPE : {
plpgsql_ns_additem(PLPGSQL_NSTYPE_RECORD, dno, ident);
} break;
case PLPGSQL_DTYPE_VARRAY : {
plpgsql_ns_additem(PLPGSQL_NSTYPE_VARRAY, dno, ident);
} break;
case PLPGSQL_DTYPE_TABLE : {
plpgsql_ns_additem(PLPGSQL_NSTYPE_TABLE, dno, ident);
} break;
default : {
yyerror("not recognized type when add package type to procedure namespace");
} break;
}
return;
}
static void AddNamespaceIfPkgVar(const char* ident, IdentifierLookup save_IdentifierLookup)
{
if (getCompileStatus() != COMPILIE_PKG_FUNC) {
return;
}
/* only declare session need to add */
if (save_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE) {
return;
}
if (ident == NULL) {
yyerror("null string when add package variable to procedure namespace");
}
if (plpgsql_ns_lookup(plpgsql_ns_top(), true, ident, NULL, NULL, NULL) != NULL) {
return;
}
PLpgSQL_nsitem* ns = NULL;
PLpgSQL_compile_context* curr_compile = u_sess->plsql_cxt.curr_compile_context;
if (curr_compile->plpgsql_curr_compile_package != NULL) {
PLpgSQL_package* pkg = curr_compile->plpgsql_curr_compile_package;
ns = plpgsql_ns_lookup(pkg->public_ns, false, ident, NULL, NULL, NULL);
if (ns == NULL) {
ns = plpgsql_ns_lookup(pkg->private_ns, false, ident, NULL, NULL, NULL);
}
}
if (ns != NULL && (ns->itemtype == PLPGSQL_NSTYPE_VAR || ns->itemtype == PLPGSQL_NSTYPE_ROW)) {
plpgsql_ns_additem(ns->itemtype, ns->itemno, ident);
}
return;
}
static void get_datum_tok_type(PLpgSQL_datum* target, int* tok_flag)
{
if (target->dtype == PLPGSQL_DTYPE_VAR) {
PLpgSQL_var* var = (PLpgSQL_var*)(target);
if (var != NULL && var->datatype != NULL &&
var->datatype->typinput.fn_oid == F_ARRAY_IN) {
if (var->datatype->collectionType == PLPGSQL_COLLECTION_TABLE) {
*tok_flag = PLPGSQL_TOK_TABLE_VAR;
} else {
*tok_flag = PLPGSQL_TOK_VARRAY_VAR;
}
} else if (var != NULL && var->datatype &&
var->datatype->typinput.fn_oid == F_DOMAIN_IN) {
HeapTuple type_tuple =
SearchSysCache1(TYPEOID, ObjectIdGetDatum(var->datatype->typoid));
if (HeapTupleIsValid(type_tuple)) {
Form_pg_type type_form = (Form_pg_type)GETSTRUCT(type_tuple);
if (F_ARRAY_OUT == type_form->typoutput) {
*tok_flag = PLPGSQL_TOK_VARRAY_VAR;
}
}
ReleaseSysCache(type_tuple);
}
}
}
static void plpgsql_cast_reference_list(List* idents, StringInfoData* ds, bool isPkgVar)
{
char* word = NULL;
if (isPkgVar) {
word = strVal(linitial(idents));
appendStringInfoString(ds, "(");
appendStringInfoString(ds, word);
appendStringInfoString(ds, ".");
word = strVal(lsecond(idents));
appendStringInfoString(ds, word);
word = strVal(lthird(idents));
appendStringInfoString(ds, ".");
appendStringInfoString(ds, word);
appendStringInfoString(ds, ")");
word = strVal(lfourth(idents));
appendStringInfoString(ds, ".");
appendStringInfoString(ds, word);
appendStringInfoString(ds, " ");
} else {
word = strVal(linitial(idents));
appendStringInfoString(ds, "(");
appendStringInfoString(ds, word);
appendStringInfoString(ds, ".");
word = strVal(lsecond(idents));
appendStringInfoString(ds, word);
appendStringInfoString(ds, ")");
word = strVal(lthird(idents));
appendStringInfoString(ds, ".");
appendStringInfoString(ds, word);
if (list_length(idents) == 4) {
word = strVal(lfourth(idents));
appendStringInfoString(ds, ".");
appendStringInfoString(ds, word);
}
appendStringInfoString(ds, " ");
}
}
static void CastArrayNameToArrayFunc(StringInfoData* ds, List* idents, bool needDot)
{
char* arrayName1 = NULL;
char* arrayName2 = NULL;
char* arrayName3 = NULL;
switch (list_length(idents)) {
case 2: {
arrayName1 = strVal(linitial(idents));
appendStringInfo(ds, "\"%s\"", arrayName1);
} break;
case 3: {
arrayName1 = strVal(linitial(idents));
arrayName2 = strVal(lsecond(idents));
appendStringInfo(ds, "\"%s\".\"%s\"", arrayName1, arrayName2);
} break;
case 4: {
arrayName1 = strVal(linitial(idents));
arrayName2 = strVal(lsecond(idents));
arrayName3 = strVal(lthird(idents));
appendStringInfo(ds, "\"%s\".\"%s\".\"%s\"", arrayName1, arrayName2, arrayName3);
} break;
default: {
yyerror("syntax error of array functions");
} break;
}
if (needDot) {
appendStringInfo(ds, ", ");
} else {
appendStringInfo(ds, " ");
}
return;
}
static bool PkgVarNeedCast(List* idents)
{
/* only pkg.row.col1.col2 need cast */
if (list_length(idents) != 4) {
return false;
}
char* word1 = strVal(linitial(idents));
char* word2 = strVal(lsecond(idents));
char* word3 = strVal(lthird(idents));
PLpgSQL_nsitem* ns = NULL;
int nnames = 0;
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, word1, word2, word3, &nnames);
/* nnames = 2, means pkg.row.col2.col2 form */
if (nnames == 2) {
return true;
}
return false;
}
static void check_autonomous_nest_tablevar(PLpgSQL_var* var)
{
if (unlikely(var->ispkg && var->nest_table != NULL &&
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile != NULL &&
u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile->is_autonomous)) {
ereport(errstate, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Un-support feature: nest tableof variable %s not support pass through autonm function",
var->varname)));
}
}
static void SetErrorState()
{
#ifndef ENABLE_MULTIPLE_NODES
if (u_sess->attr.attr_common.plsql_show_all_error) {
errstate = NOTICE;
} else {
errstate = ERROR;
}
#else
errstate = ERROR;
#endif
}
static bool need_build_row_for_func_arg(PLpgSQL_rec **rec, PLpgSQL_row **row, int out_arg_num, int all_arg, int *varnos, char *p_argmodes)
{
/* out arg number > 1 should build a row */
if (out_arg_num > 1) {
return true;
}
/* no row or rec, should build a row */
if (*rec == NULL && *row == NULL) {
return true;
}
/* no out arg, need not build */
if (out_arg_num == 0) {
return false;
}
PLpgSQL_compile_context* curr_compile = u_sess->plsql_cxt.curr_compile_context;
PLpgSQL_datum *tempDatum = NULL;
for (int i = 0; i < all_arg; i++) {
if (p_argmodes[i] == 'i') {
continue;
}
/* out param no destination */
if (varnos[i] == -1) {
*rec = NULL;
*row = NULL;
return false;
}
tempDatum = curr_compile->plpgsql_Datums[varnos[i]];
/* the out param is the rec or row */
if (tempDatum == (PLpgSQL_datum*)(*rec) || tempDatum == (PLpgSQL_datum*)(*row)) {
return false;
}
/* the out param is scalar, need build row */
if (tempDatum->dtype == PLPGSQL_DTYPE_VAR
|| tempDatum->dtype == PLPGSQL_DTYPE_RECFIELD
|| tempDatum->dtype == PLPGSQL_DTYPE_ASSIGNLIST) {
return true;
}
/* need not build a new row, but need to replace the correct row */
if (tempDatum->dtype == PLPGSQL_DTYPE_ROW || tempDatum->dtype == PLPGSQL_DTYPE_RECORD) {
*row = (PLpgSQL_row *)tempDatum;
return false;
}
if (tempDatum->dtype == PLPGSQL_DTYPE_REC) {
*rec = (PLpgSQL_rec *)tempDatum;
return false;
}
/* arrive here, means out param invalid, set row and rec to null*/
*rec = NULL;
*row = NULL;
return false;
}
/* should not arrive here */
*rec = NULL;
*row = NULL;
return false;
}
#ifndef ENABLE_MULTIPLE_NODES
static void BuildForQueryVariable(PLpgSQL_expr* expr, PLpgSQL_row **row, PLpgSQL_rec **rec,
const char* refname, int lineno)
{
TupleDesc desc = getCursorTupleDesc(expr, true);
if (desc == NULL || desc->natts == 0 || checkAllAttrName(desc)) {
PLpgSQL_type dtype;
dtype.ttype = PLPGSQL_TTYPE_REC;
*rec = (PLpgSQL_rec *)
plpgsql_build_variable(refname, lineno, &dtype, true);
} else {
*row = build_row_from_tuple_desc(refname, lineno, desc);
}
}
#endif