📄 gram.y
字号:
}/* * Read a SQL construct and build a PLpgSQL_expr for it. * * until: token code for expected terminator * until2: token code for alternate terminator (pass 0 if none) * expected: text to use in complaining that terminator was not found * sqlstart: text to prefix to the accumulated SQL text * isexpression: whether to say we're reading an "expression" or a "statement" * valid_sql: whether to check the syntax of the expr (prefixed with sqlstart) * endtoken: if not NULL, ending token is stored at *endtoken * (this is only interesting if until2 isn't zero) */static PLpgSQL_expr *read_sql_construct(int until, int until2, const char *expected, const char *sqlstart, bool isexpression, bool valid_sql, int *endtoken){ int tok; int lno; PLpgSQL_dstring ds; int parenlevel = 0; int nparams = 0; int params[MAX_EXPR_PARAMS]; char buf[32]; PLpgSQL_expr *expr; lno = plpgsql_scanner_lineno(); plpgsql_dstring_init(&ds); plpgsql_dstring_append(&ds, sqlstart); for (;;) { tok = yylex(); if (tok == until && parenlevel == 0) break; if (tok == until2 && parenlevel == 0) break; if (tok == '(' || tok == '[') parenlevel++; else if (tok == ')' || tok == ']') { parenlevel--; if (parenlevel < 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("mismatched parentheses"))); } /* * End of function definition is an error, and we don't expect to * hit a semicolon either (unless it's the until symbol, in which * case we should have fallen out above). */ if (tok == 0 || tok == ';') { plpgsql_error_lineno = lno; if (parenlevel != 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("mismatched parentheses"))); if (isexpression) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("missing \"%s\" at end of SQL expression", expected))); else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("missing \"%s\" at end of SQL statement", expected))); } if (plpgsql_SpaceScanned) plpgsql_dstring_append(&ds, " "); switch (tok) { case T_SCALAR: snprintf(buf, sizeof(buf), " $%d ", assign_expr_param(yylval.scalar->dno, params, &nparams)); plpgsql_dstring_append(&ds, buf); break; case T_ROW: snprintf(buf, sizeof(buf), " $%d ", assign_expr_param(yylval.row->rowno, params, &nparams)); plpgsql_dstring_append(&ds, buf); break; case T_RECORD: snprintf(buf, sizeof(buf), " $%d ", assign_expr_param(yylval.rec->recno, params, &nparams)); plpgsql_dstring_append(&ds, buf); break; default: plpgsql_dstring_append(&ds, yytext); break; } } if (endtoken) *endtoken = tok; expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int)); expr->dtype = PLPGSQL_DTYPE_EXPR; expr->query = pstrdup(plpgsql_dstring_get(&ds)); expr->plan = NULL; expr->nparams = nparams; while(nparams-- > 0) expr->params[nparams] = params[nparams]; plpgsql_dstring_free(&ds); if (valid_sql) check_sql_expr(expr->query); return expr;}static PLpgSQL_type *read_datatype(int tok){ int lno; PLpgSQL_dstring ds; PLpgSQL_type *result; bool needspace = false; int parenlevel = 0; lno = plpgsql_scanner_lineno(); /* Often there will be a lookahead token, but if not, get one */ if (tok == YYEMPTY) tok = yylex(); if (tok == T_DTYPE) { /* lexer found word%TYPE and did its thing already */ return yylval.dtype; } plpgsql_dstring_init(&ds); while (tok != ';') { if (tok == 0) { plpgsql_error_lineno = lno; if (parenlevel != 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("mismatched parentheses"))); ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("incomplete datatype declaration"))); } /* Possible followers for datatype in a declaration */ if (tok == K_NOT || tok == K_ASSIGN || tok == K_DEFAULT) break; /* Possible followers for datatype in a cursor_arg list */ if ((tok == ',' || tok == ')') && parenlevel == 0) break; if (tok == '(') parenlevel++; else if (tok == ')') parenlevel--; if (needspace) plpgsql_dstring_append(&ds, " "); needspace = true; plpgsql_dstring_append(&ds, yytext); tok = yylex(); } plpgsql_push_back_token(tok); plpgsql_error_lineno = lno; /* in case of error in parse_datatype */ result = plpgsql_parse_datatype(plpgsql_dstring_get(&ds)); plpgsql_dstring_free(&ds); return result;}static PLpgSQL_stmt *make_select_stmt(void){ PLpgSQL_dstring ds; int nparams = 0; int params[MAX_EXPR_PARAMS]; char buf[32]; PLpgSQL_expr *expr; PLpgSQL_row *row = NULL; PLpgSQL_rec *rec = NULL; int tok; bool have_into = false; plpgsql_dstring_init(&ds); plpgsql_dstring_append(&ds, "SELECT "); while (1) { tok = yylex(); if (tok == ';') break; if (tok == 0) { plpgsql_error_lineno = plpgsql_scanner_lineno(); ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unexpected end of function definition"))); } if (tok == K_INTO) { if (have_into) { plpgsql_error_lineno = plpgsql_scanner_lineno(); ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("INTO specified more than once"))); } tok = yylex(); switch (tok) { case T_ROW: row = yylval.row; have_into = true; break; case T_RECORD: rec = yylval.rec; have_into = true; break; case T_SCALAR: row = read_into_scalar_list(yytext, yylval.scalar); have_into = true; break; default: /* Treat the INTO as non-special */ plpgsql_dstring_append(&ds, " INTO "); plpgsql_push_back_token(tok); break; } 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); if (have_into) { PLpgSQL_stmt_select *select; select = palloc0(sizeof(PLpgSQL_stmt_select)); select->cmd_type = PLPGSQL_STMT_SELECT; select->rec = rec; select->row = row; select->query = expr; return (PLpgSQL_stmt *)select; } else { PLpgSQL_stmt_execsql *execsql; execsql = palloc(sizeof(PLpgSQL_stmt_execsql)); execsql->cmd_type = PLPGSQL_STMT_EXECSQL; execsql->sqlstmt = expr; return (PLpgSQL_stmt *)execsql; }}static PLpgSQL_stmt *make_fetch_stmt(void){ int tok; PLpgSQL_row *row = NULL; PLpgSQL_rec *rec = NULL; PLpgSQL_stmt_fetch *fetch; /* We have already parsed everything through the INTO keyword */ tok = yylex(); switch (tok) { case T_ROW: row = yylval.row; break; case T_RECORD: rec = yylval.rec; break; case T_SCALAR: row = read_into_scalar_list(yytext, yylval.scalar); break; default: yyerror("syntax error"); } tok = yylex(); if (tok != ';') yyerror("syntax error"); fetch = palloc0(sizeof(PLpgSQL_stmt_select)); fetch->cmd_type = PLPGSQL_STMT_FETCH; fetch->rec = rec; fetch->row = row; return (PLpgSQL_stmt *)fetch;}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; }}/* * 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 variable", yytext))); } } /* * We read an extra, non-comma character 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;}/* * 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 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))); } }}#include "pl_scan.c"
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -