📄 gram.y
字号:
if ((tok == ',' || tok == ')') && parenlevel == 0) break; if (tok == '(') parenlevel++; else if (tok == ')') parenlevel--; if (needspace) plpgsql_dstring_append(&ds, " "); needspace = true; plpgsql_dstring_append(&ds, yytext); tok = yylex(); } plpgsql_push_back_token(tok); type_name = plpgsql_dstring_get(&ds); if (type_name[0] == '\0') yyerror("missing datatype declaration"); plpgsql_error_lineno = lno; /* in case of error in parse_datatype */ result = plpgsql_parse_datatype(type_name); plpgsql_dstring_free(&ds); return result;}static PLpgSQL_stmt *make_execsql_stmt(const char *sqlstart, int lineno){ PLpgSQL_dstring ds; int nparams = 0; int params[MAX_EXPR_PARAMS]; char buf[32]; PLpgSQL_stmt_execsql *execsql; PLpgSQL_expr *expr; PLpgSQL_row *row = NULL; PLpgSQL_rec *rec = NULL; int tok; bool have_into = false; bool have_strict = false; plpgsql_dstring_init(&ds); plpgsql_dstring_append(&ds, sqlstart); for (;;) { tok = yylex(); if (tok == ';') break; if (tok == 0) yyerror("unexpected end of function definition"); if (tok == K_INTO) { if (have_into) yyerror("INTO specified more than once"); have_into = true; read_into_target(&rec, &row, &have_strict); continue; } if (plpgsql_SpaceScanned) plpgsql_dstring_append(&ds, " "); switch (tok) { case T_SCALAR: snprintf(buf, sizeof(buf), " $%d ", assign_expr_param(yylval.scalar->dno, params, &nparams)); plpgsql_dstring_append(&ds, buf); break; case T_ROW: snprintf(buf, sizeof(buf), " $%d ", assign_expr_param(yylval.row->rowno, params, &nparams)); plpgsql_dstring_append(&ds, buf); break; case T_RECORD: snprintf(buf, sizeof(buf), " $%d ", assign_expr_param(yylval.rec->recno, params, &nparams)); plpgsql_dstring_append(&ds, buf); break; default: plpgsql_dstring_append(&ds, yytext); break; } } expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int)); expr->dtype = PLPGSQL_DTYPE_EXPR; expr->query = pstrdup(plpgsql_dstring_get(&ds)); expr->plan = NULL; expr->nparams = nparams; while(nparams-- > 0) expr->params[nparams] = params[nparams]; plpgsql_dstring_free(&ds); check_sql_expr(expr->query); execsql = palloc(sizeof(PLpgSQL_stmt_execsql)); execsql->cmd_type = PLPGSQL_STMT_EXECSQL; execsql->lineno = lineno; execsql->sqlstmt = expr; execsql->into = have_into; execsql->strict = have_strict; execsql->rec = rec; execsql->row = row; return (PLpgSQL_stmt *) execsql;}static PLpgSQL_stmt_fetch *read_fetch_direction(void){ PLpgSQL_stmt_fetch *fetch; int tok; bool check_FROM = true; /* * We create the PLpgSQL_stmt_fetch struct here, but only fill in * the fields arising from the optional direction clause */ fetch = (PLpgSQL_stmt_fetch *) palloc0(sizeof(PLpgSQL_stmt_fetch)); fetch->cmd_type = PLPGSQL_STMT_FETCH; /* set direction defaults: */ fetch->direction = FETCH_FORWARD; fetch->how_many = 1; fetch->expr = NULL; /* * Most of the direction keywords are not plpgsql keywords, so we * rely on examining yytext ... */ tok = yylex(); if (tok == 0) yyerror("unexpected end of function definition"); if (pg_strcasecmp(yytext, "next") == 0) { /* use defaults */ } else if (pg_strcasecmp(yytext, "prior") == 0) { fetch->direction = FETCH_BACKWARD; } else if (pg_strcasecmp(yytext, "first") == 0) { fetch->direction = FETCH_ABSOLUTE; } else if (pg_strcasecmp(yytext, "last") == 0) { fetch->direction = FETCH_ABSOLUTE; fetch->how_many = -1; } else if (pg_strcasecmp(yytext, "absolute") == 0) { fetch->direction = FETCH_ABSOLUTE; fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN", "SELECT ", true, true, NULL); check_FROM = false; } else if (pg_strcasecmp(yytext, "relative") == 0) { fetch->direction = FETCH_RELATIVE; fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN", "SELECT ", true, true, NULL); check_FROM = false; } else if (pg_strcasecmp(yytext, "forward") == 0) { /* use defaults */ } else if (pg_strcasecmp(yytext, "backward") == 0) { fetch->direction = FETCH_BACKWARD; } else if (tok != T_SCALAR) { plpgsql_push_back_token(tok); fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN", "SELECT ", true, true, NULL); check_FROM = false; } else { /* Assume there's no direction clause */ plpgsql_push_back_token(tok); check_FROM = false; } /* check FROM or IN keyword after direction's specification */ if (check_FROM) { tok = yylex(); if (tok != K_FROM && tok != K_IN) yyerror("expected FROM or IN"); } return fetch;}static PLpgSQL_stmt *make_return_stmt(int lineno){ PLpgSQL_stmt_return *new; new = palloc0(sizeof(PLpgSQL_stmt_return)); new->cmd_type = PLPGSQL_STMT_RETURN; new->lineno = lineno; new->expr = NULL; new->retvarno = -1; if (plpgsql_curr_compile->fn_retset) { if (yylex() != ';') yyerror("RETURN cannot have a parameter in function " "returning set; use RETURN NEXT or RETURN QUERY"); } else if (plpgsql_curr_compile->out_param_varno >= 0) { if (yylex() != ';') yyerror("RETURN cannot have a parameter in function with OUT parameters"); new->retvarno = plpgsql_curr_compile->out_param_varno; } else if (plpgsql_curr_compile->fn_rettype == VOIDOID) { if (yylex() != ';') yyerror("RETURN cannot have a parameter in function returning void"); } else if (plpgsql_curr_compile->fn_retistuple) { switch (yylex()) { case K_NULL: /* we allow this to support RETURN NULL in triggers */ break; case T_ROW: new->retvarno = yylval.row->rowno; break; case T_RECORD: new->retvarno = yylval.rec->recno; break; default: yyerror("RETURN must specify a record or row variable in function returning tuple"); break; } if (yylex() != ';') yyerror("RETURN must specify a record or row variable in function returning tuple"); } else { /* * Note that a well-formed expression is * _required_ here; anything else is a * compile-time error. */ new->expr = plpgsql_read_expression(';', ";"); } return (PLpgSQL_stmt *) new;}static PLpgSQL_stmt *make_return_next_stmt(int lineno){ PLpgSQL_stmt_return_next *new; if (!plpgsql_curr_compile->fn_retset) yyerror("cannot use RETURN NEXT in a non-SETOF function"); new = palloc0(sizeof(PLpgSQL_stmt_return_next)); new->cmd_type = PLPGSQL_STMT_RETURN_NEXT; new->lineno = lineno; new->expr = NULL; new->retvarno = -1; if (plpgsql_curr_compile->out_param_varno >= 0) { if (yylex() != ';') yyerror("RETURN NEXT cannot have a parameter in function with OUT parameters"); new->retvarno = plpgsql_curr_compile->out_param_varno; } else if (plpgsql_curr_compile->fn_retistuple) { switch (yylex()) { case T_ROW: new->retvarno = yylval.row->rowno; break; case T_RECORD: new->retvarno = yylval.rec->recno; break; default: yyerror("RETURN NEXT must specify a record or row variable in function returning tuple"); break; } if (yylex() != ';') yyerror("RETURN NEXT must specify a record or row variable in function returning tuple"); } else new->expr = plpgsql_read_expression(';', ";"); return (PLpgSQL_stmt *) new;}static PLpgSQL_stmt *make_return_query_stmt(int lineno){ PLpgSQL_stmt_return_query *new; if (!plpgsql_curr_compile->fn_retset) yyerror("cannot use RETURN QUERY in a non-SETOF function"); new = palloc0(sizeof(PLpgSQL_stmt_return_query)); new->cmd_type = PLPGSQL_STMT_RETURN_QUERY; new->lineno = lineno; new->query = read_sql_construct(';', 0, ")", "", false, true, NULL); return (PLpgSQL_stmt *) new;}static voidcheck_assignable(PLpgSQL_datum *datum){ switch (datum->dtype) { case PLPGSQL_DTYPE_VAR: if (((PLpgSQL_var *) datum)->isconst) { plpgsql_error_lineno = plpgsql_scanner_lineno(); ereport(ERROR, (errcode(ERRCODE_ERROR_IN_ASSIGNMENT), errmsg("\"%s\" is declared CONSTANT", ((PLpgSQL_var *) datum)->refname))); } break; case PLPGSQL_DTYPE_ROW: /* always assignable? */ break; case PLPGSQL_DTYPE_REC: /* always assignable? What about NEW/OLD? */ break; case PLPGSQL_DTYPE_RECFIELD: /* always assignable? */ break; case PLPGSQL_DTYPE_ARRAYELEM: /* always assignable? */ break; case PLPGSQL_DTYPE_TRIGARG: yyerror("cannot assign to tg_argv"); break; default: elog(ERROR, "unrecognized dtype: %d", datum->dtype); break; }}/* * Read the argument of an INTO clause. On entry, we have just read the * INTO keyword. */static voidread_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict){ int tok; /* Set default results */ *rec = NULL; *row = NULL; if (strict) *strict = false; tok = yylex(); if (strict && tok == K_STRICT) { *strict = true; tok = yylex(); } switch (tok) { case T_ROW: *row = yylval.row; check_assignable((PLpgSQL_datum *) *row); break; case T_RECORD: *rec = yylval.rec; check_assignable((PLpgSQL_datum *) *rec); break; case T_SCALAR: *row = read_into_scalar_list(yytext, yylval.scalar); break; default: plpgsql_error_lineno = plpgsql_scanner_lineno(); ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("syntax error at \"%s\"", yytext), errdetail("Expected record variable, row variable, " "or list of scalar variables following INTO."))); }}/* * Given the first datum and name in the INTO list, continue to read * comma-separated scalar variables until we run out. Then construct * and return a fake "row" variable that represents the list of * scalars. */static PLpgSQL_row *read_into_scalar_list(const char *initial_name, PLpgSQL_datum *initial_datum){ int nfields; char *fieldnames[1024]; int varnos[1024]; PLpgSQL_row *row; int tok; check_assignable(initial_datum); fieldnames[0] = pstrdup(initial_name); varnos[0] = initial_datum->dno; nfields = 1; while ((tok = yylex()) == ',') { /* Check for array overflow */ if (nfields >= 1024) { plpgsql_error_lineno = plpgsql_scanner_lineno(); ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("too many INTO variables specified"))); } tok = yylex(); switch(tok) { case T_SCALAR: check_assignable(yylval.scalar); fieldnames[nfields] = pstrdup(yytext); varnos[nfields++] = yylval.scalar->dno; break; default: plpgsql_error_lineno = plpgsql_scanner_lineno(); ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("\"%s\" is not a scalar variable", yytext))); } } /* * We read an extra, non-comma token from yylex(), so push it * back onto the input stream */ plpgsql_push_back_token(tok); row = palloc(sizeof(PLpgSQL_row)); row->dtype = PLPGSQL_DTYPE_ROW; row->refname = pstrdup("*internal*"); row->lineno = plpgsql_scanner_lineno(); row->rowtupdesc = NULL; row->nfields = nfields; row->fieldnames = palloc(sizeof(char *) * nfields); row->varnos = palloc(sizeof(int) * nfields); while (--nfields >= 0) { row->fieldnames[nfields] = fieldnames[nfields]; row->varnos[nfields] = varnos[nfields]; } plpgsql_adddatum((PLpgSQL_datum *)row); return row;}/* * Convert a single scalar into a "row" list. This is exactly * like read_into_scalar_list except we never consume any input. * In fact, since this can be invoked long after the source * input was actually read, the lineno has to be passed in. */static PLpgSQL_row *make_scalar_list1(const char *initial_name, PLpgSQL_datum *initial_datum, int lineno){ PLpgSQL_row *row; check_assignable(initial_datum); row = palloc(sizeof(PLpgSQL_row)); row->dtype = PLPGSQL_DTYPE_ROW; row->refname = pstrdup("*internal*"); row->lineno = lineno; row->rowtupdesc = NULL; row->nfields = 1; row->fieldnames = palloc(sizeof(char *)); row->varnos = palloc(sizeof(int)); row->fieldnames[0] = pstrdup(initial_name); row->varnos[0] = initial_datum->dno; plpgsql_adddatum((PLpgSQL_datum *)row); return row;}/* * When the PL/PgSQL parser expects to see a SQL statement, it is very * liberal in what it accepts; for example, we often assume an * unrecognized keyword is the beginning of a SQL statement. This * avoids the need to duplicate parts of the SQL grammar in the * PL/PgSQL grammar, but it means we can accept wildly malformed * input. To try and catch some of the more obviously invalid input, * we run the strings we expect to be SQL statements through the main * SQL parser. * * We only invoke the raw parser (not the analyzer); this doesn't do * any database access and does not check any semantic rules, it just * checks for basic syntactic correctness. We do this here, rather * than after parsing has finished, because a malformed SQL statement * may cause the PL/PgSQL parser to become confused about statement * borders. So it is best to bail out as early as we can. */static voidcheck_sql_expr(const char *stmt){ ErrorContextCallback syntax_errcontext; ErrorContextCallback *previous_errcontext; MemoryContext oldCxt; if (!plpgsql_check_syntax) return; /* * Setup error traceback support for ereport(). The previous * ereport callback is installed by pl_comp.c, but we don't want * that to be invoked (since it will try to transpose the syntax * error to be relative to the CREATE FUNCTION), so temporarily * remove it from the list of callbacks. */ Assert(error_context_stack->callback == plpgsql_compile_error_callback); previous_errcontext = error_context_stack; syntax_errcontext.callback = plpgsql_sql_error_callback; syntax_errcontext.arg = (char *) stmt; syntax_errcontext.previous = error_context_stack->previous; error_context_stack = &syntax_errcontext; oldCxt = MemoryContextSwitchTo(compile_tmp_cxt); (void) raw_parser(stmt); MemoryContextSwitchTo(oldCxt); /* Restore former ereport callback */ error_context_stack = previous_errcontext;}static voidplpgsql_sql_error_callback(void *arg){ char *sql_stmt = (char *) arg; Assert(plpgsql_error_funcname); errcontext("SQL statement in PL/PgSQL function \"%s\" near line %d", plpgsql_error_funcname, plpgsql_error_lineno); internalerrquery(sql_stmt); internalerrposition(geterrposition()); errposition(0);}static char *check_label(const char *yytxt){ char *label_name; plpgsql_convert_ident(yytxt, &label_name, 1); if (plpgsql_ns_lookup_label(label_name) == NULL) yyerror("no such label"); return label_name;}static voidcheck_labels(const char *start_label, const char *end_label){ if (end_label) { if (!start_label) { plpgsql_error_lineno = plpgsql_scanner_lineno(); ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("end label \"%s\" specified for unlabelled block", end_label))); } if (strcmp(start_label, end_label) != 0) { plpgsql_error_lineno = plpgsql_scanner_lineno(); ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("end label \"%s\" differs from block's label \"%s\"", end_label, start_label))); } }}/* Needed to avoid conflict between different prefix settings: */#undef yylex#include "pl_scan.c"
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -