* Copyright (c) Huawei Technologies Co., Ltd. 2024-2026. All rights reserved.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* --------------------------------------------------------------------------------------
*
* pl_scanner.c
* lexical scanning for PL/pgSQL
*
*
* Portions Copyright (c) 2026, Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 2020, AWS
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* contrib/shark/src/pltsql/pl_scanner.c
*
* -------------------------------------------------------------------------
*/
#include "c.h"
#include "pltsql.h"
#include "pl_gram.hpp"
#include "catalog/pg_object.h"
#include "src/backend_parser/scanner.h"
* A word about keywords:
*
* We keep reserved and unreserved keywords in separate headers. Be careful
* not to put the same word in both headers. Also be sure that pl_gram.y's
* unreserved_keyword production agrees with the unreserved header. The
* reserved keywords are passed to the core scanner, so they will be
* recognized before (and instead of) any variable name. Unreserved
* words are checked for separately, after determining that the identifier
* isn't a known variable name. If plpgsql_IdentifierLookup is DECLARE then
* no variable names will be recognized, so the unreserved words always work.
* (Note in particular that this helps us avoid reserving keywords that are
* only needed in DECLARE sections.)
*
* In certain contexts it is desirable to prefer recognizing an unreserved
* keyword over recognizing a variable name. Those cases are handled in
* gram.y using tok_is_keyword().
*
* For the most part, the reserved keywords are those that start a PL/pgSQL
* statement (and so would conflict with an assignment to a variable of the
* same name). We also don't sweat it much about reserving keywords that
* are reserved in the core grammar. Try to avoid reserving other words.
*/
* Lists of keyword (name, token-value, category) entries.
*
* !!WARNING!!: These lists must be sorted by ASCII name, because binary
* search is used to locate entries.
*
* Be careful not to put the same word in both lists. Also be sure that
* gram.y's unreserved_keyword production agrees with the second list.
*/
#include "pl_reserved_kwlist_d.h"
#include "pl_unreserved_kwlist_d.h"
#define PG_KEYWORD(kwname, value) value,
static const uint16 ReservedPLKeywordTokens[] = {
#include "pl_reserved_kwlist.h"
};
static const uint16 UnreservedPLKeywordTokens[] = {
#include "pl_unreserved_kwlist.h"
};
#undef PG_KEYWORD
static const struct PlpgsqlKeywordValue keywordsValue = {
.procedure = K_PROCEDURE,
.function = K_FUNCTION,
.begin = K_BEGIN,
.select = K_SELECT,
.update = K_UPDATE,
.insert = K_INSERT,
.Delete = K_DELETE,
.merge = K_MERGE
};
typedef struct {
YYSTYPE lval;
YYLTYPE lloc;
int leng;
} TokenAuxData;
THR_LOCAL TokenAuxData pushback_auxdata[MAX_PUSHBACKS];
static int internal_yylex(TokenAuxData* auxdata);
static void push_back_token(int token, TokenAuxData* auxdata);
static void location_lineno_init(void);
static int get_self_defined_tok(int tok_flag);
static int plpgsql_parse_cursor_attribute(int* loc);
static int plpgsql_parse_declare(int* loc);
* This is the yylex routine called from the PL/pgSQL grammar.
* It is a wrapper around the core lexer, with the ability to recognize
* PL/pgSQL variables and return them as special T_DATUM tokens. If a
* word or compound word does not match any variable name, or if matching
* is turned off by plpgsql_IdentifierLookup, it is returned as
* T_WORD or T_CWORD respectively, or as an unreserved keyword if it
* matches one of those.
*/
int pltsql_yylex(void)
{
int tok1;
int loc = 0;
TokenAuxData aux1;
int kwnum;
int tok_flag = -1;
int dbl_tok_flag = -1;
int trip_tok_flag = -1;
int quad_tok_flag = -1;
tok1 = plpgsql_parse_cursor_attribute(&loc);
if (tok1 != -1) {
plpgsql_yylloc = loc;
return tok1;
}
if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) {
tok1 = plpgsql_parse_declare(&loc);
if (tok1 != -1) {
plpgsql_yylloc = loc;
return tok1;
}
}
tok1 = internal_yylex(&aux1);
if (tok1 == IDENT || tok1 == PARAM || tok1 == T_SQL_BULK_EXCEPTIONS) {
int tok2;
TokenAuxData aux2;
char* tok1_val = NULL;
if(tok1 == PARAM)
tok1_val = aux1.lval.str;
tok2 = internal_yylex(&aux2);
if (tok2 == '.') {
int tok3;
TokenAuxData aux3;
tok3 = internal_yylex(&aux3);
if (tok3 == IDENT || (tok1 == IDENT && (tok3 == K_DELETE || tok3 == K_CLOSE || tok3 == K_OPEN))) {
int tok4;
TokenAuxData aux4;
tok4 = internal_yylex(&aux4);
if (tok4 == '.') {
int tok5;
TokenAuxData aux5;
tok5 = internal_yylex(&aux5);
if (tok5 == IDENT || (tok5 == K_DELETE && tok3 == IDENT && tok1 == IDENT)) {
int tok6;
TokenAuxData aux6;
tok6 = internal_yylex(&aux6);
if (tok6 == '.') {
int tok7;
TokenAuxData aux7;
tok7 = internal_yylex(&aux7);
if (tok7 == IDENT ||
(tok7 == K_DELETE && tok5 == IDENT && tok3 == IDENT && tok1 == IDENT)) {
if (plpgsql_parse_quadword(aux1.lval.str, aux3.lval.str, aux5.lval.str, aux7.lval.str,
&aux1.lval.wdatum, &aux1.lval.cword, &quad_tok_flag)) {
if (quad_tok_flag != -1) {
tok1 = get_self_defined_tok(quad_tok_flag);
} else {
tok1 = T_DATUM;
}
} else {
tok1 = T_CWORD;
}
} else {
push_back_token(tok7, &aux7);
push_back_token(tok6, &aux6);
if (plpgsql_parse_tripword(aux1.lval.str, aux3.lval.str, aux5.lval.str,
&aux1.lval.wdatum, &aux1.lval.cword, &trip_tok_flag)) {
if (trip_tok_flag != -1) {
tok1 = get_self_defined_tok(trip_tok_flag);
} else {
tok1 = T_DATUM;
}
} else {
tok1 = T_CWORD;
}
}
} else {
push_back_token(tok6, &aux6);
if (plpgsql_parse_tripword(aux1.lval.str, aux3.lval.str, aux5.lval.str,
&aux1.lval.wdatum, &aux1.lval.cword, &trip_tok_flag)) {
if (trip_tok_flag != -1) {
tok1 = get_self_defined_tok(trip_tok_flag);
} else {
tok1 = T_DATUM;
}
} else {
tok1 = T_CWORD;
}
}
} else {
push_back_token(tok5, &aux5);
push_back_token(tok4, &aux4);
if (plpgsql_parse_dblword(
aux1.lval.str, aux3.lval.str, &aux1.lval.wdatum, &aux1.lval.cword, &dbl_tok_flag)) {
if (dbl_tok_flag != -1) {
tok1 = get_self_defined_tok(dbl_tok_flag);
} else {
tok1 = T_DATUM;
}
} else {
tok1 = T_CWORD;
}
}
} else {
push_back_token(tok4, &aux4);
if (plpgsql_parse_dblword(
aux1.lval.str, aux3.lval.str, &aux1.lval.wdatum, &aux1.lval.cword, &dbl_tok_flag)) {
if (dbl_tok_flag != -1) {
tok1 = get_self_defined_tok(dbl_tok_flag);
} else {
tok1 = T_DATUM;
}
} else {
tok1 = T_CWORD;
}
}
} else {
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
if (plpgsql_parse_word(
aux1.lval.str, u_sess->plsql_cxt.curr_compile_context->core_yy->scanbuf + aux1.lloc, &aux1.lval.wdatum,
&aux1.lval.word, &tok_flag)) {
if (tok_flag != -1) {
tok1 = get_self_defined_tok(tok_flag);
} else {
tok1 = T_DATUM;
}
} else if (!aux1.lval.word.quoted &&
(kwnum = ScanKeywordLookup(aux1.lval.word.ident,
&UnreservedPLKeywords)) >= 0) {
aux1.lval.keyword = GetScanKeyword(kwnum,
&UnreservedPLKeywords);
tok1 = UnreservedPLKeywordTokens[kwnum];
} else {
tok1 = T_WORD;
}
}
} else {
push_back_token(tok2, &aux2);
if (plpgsql_parse_word(
aux1.lval.str, u_sess->plsql_cxt.curr_compile_context->core_yy->scanbuf + aux1.lloc, &aux1.lval.wdatum,
&aux1.lval.word, &tok_flag)) {
if (tok_flag != -1) {
tok1 = get_self_defined_tok(tok_flag);
} else {
tok1 = T_DATUM;
}
} else if (!aux1.lval.word.quoted &&
(kwnum = ScanKeywordLookup(aux1.lval.word.ident,
&UnreservedPLKeywords)) >= 0) {
aux1.lval.keyword = GetScanKeyword(kwnum,
&UnreservedPLKeywords);
tok1 = UnreservedPLKeywordTokens[kwnum];
} else if (aux1.lval.str[0] == ':' && tok1 == PARAM) {
* It is a placeholder in exec statement. */
if(u_sess->attr.attr_sql.sql_compatibility == B_FORMAT)
{
if(pg_strcasecmp(tok1_val, ":loop") == 0)
tok1 = T_LABELLOOP;
else if(pg_strcasecmp(tok1_val, ":while") == 0)
tok1 = T_LABELWHILE;
else if(pg_strcasecmp(tok1_val, ":repeat") == 0)
tok1 = T_LABELREPEAT;
else
tok1 = T_PLACEHOLDER;
}
else
{
tok1 = T_PLACEHOLDER;
}
} else if ((OBJECTTYPE_MEMBER_PROC == u_sess->plsql_cxt.typfunckind
|| OBJECTTYPE_CONSTRUCTOR_PROC == u_sess->plsql_cxt.typfunckind
|| OBJECTTYPE_DEFAULT_CONSTRUCTOR_PROC == u_sess->plsql_cxt.typfunckind
|| OBJECTTYPE_MAP_PROC == u_sess->plsql_cxt.typfunckind
|| OBJECTTYPE_ORDER_PROC == u_sess->plsql_cxt.typfunckind)
&& ('=' == tok2 || COLON_EQUALS == tok2 || '[' == tok2 || '(' == tok2)) {
List* idents_bak = aux1.lval.cword.idents;
if (plpgsql_parse_dblword("self", aux1.lval.str, &aux1.lval.wdatum, &aux1.lval.cword, &dbl_tok_flag)) {
tok1 = T_DATUM;
} else {
aux1.lval.cword.idents = idents_bak;
tok1 = T_DATUM;
}
} else {
tok1 = T_WORD;
}
}
} else {
}
plpgsql_yylval = aux1.lval;
plpgsql_yylloc = aux1.lloc;
u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng = aux1.leng;
return tok1;
}
int pltsql_yylex_single(void)
{
int tok1;
int loc = 0;
TokenAuxData aux1;
int kwnum;
int tok_flag = -1;
tok1 = plpgsql_parse_cursor_attribute(&loc);
if (tok1 != -1) {
plpgsql_yylloc = loc;
return tok1;
}
tok1 = internal_yylex(&aux1);
if (tok1 == IDENT || tok1 == PARAM) {
if (plpgsql_parse_word(
aux1.lval.str, u_sess->plsql_cxt.curr_compile_context->core_yy->scanbuf + aux1.lloc, &aux1.lval.wdatum,
&aux1.lval.word, &tok_flag)) {
if (tok_flag != -1) {
tok1 = get_self_defined_tok(tok_flag);
} else {
tok1 = T_DATUM;
}
} else if (!aux1.lval.word.quoted &&
(kwnum = ScanKeywordLookup(aux1.lval.word.ident,
&UnreservedPLKeywords)) >= 0) {
aux1.lval.keyword = GetScanKeyword(kwnum,
&UnreservedPLKeywords);
tok1 = UnreservedPLKeywordTokens[kwnum];
} else {
tok1 = T_WORD;
}
} else {
}
plpgsql_yylval = aux1.lval;
plpgsql_yylloc = aux1.lloc;
u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng = aux1.leng;
return tok1;
}
* Internal yylex function. This wraps the core lexer and adds one feature:
* a token pushback stack. We also make a couple of trivial single-token
* translations from what the core lexer does to what we want, in particular
* interfacing from the core_YYSTYPE to YYSTYPE union.
*/
static int internal_yylex(TokenAuxData* auxdata)
{
int token;
const char* yytext = NULL;
errno_t rc = memset_s(auxdata, sizeof(TokenAuxData), 0, sizeof(TokenAuxData));
securec_check(rc, "\0", "\0");
Assert(u_sess->plsql_cxt.curr_compile_context);
PLpgSQL_compile_context* curr_compile = u_sess->plsql_cxt.curr_compile_context;
if (curr_compile->num_pushbacks > 0) {
curr_compile->num_pushbacks--;
token = curr_compile->pushback_token[curr_compile->num_pushbacks];
*auxdata = pushback_auxdata[curr_compile->num_pushbacks];
} else {
token = pgtsql_core_yylex(&auxdata->lval.core_yystype, &auxdata->lloc, curr_compile->yyscanner);
yytext = curr_compile->core_yy->scanbuf + auxdata->lloc;
auxdata->leng = strlen(yytext);
if (token == Op) {
if (strcmp(auxdata->lval.str, "<<") == 0) {
token = LESS_LESS;
} else if (strcmp(auxdata->lval.str, ">>") == 0) {
token = GREATER_GREATER;
} else if (strcmp(auxdata->lval.str, "#") == 0) {
token = '#';
}
} else if (token == PARAM) {
auxdata->lval.str = pstrdup(yytext);
}
}
return token;
}
* Push back a token to be re-read by next internal_yylex() call.
*/
static void push_back_token(int token, TokenAuxData* auxdata)
{
Assert(u_sess->plsql_cxt.curr_compile_context);
PLpgSQL_compile_context* curr_compile = u_sess->plsql_cxt.curr_compile_context;
if (curr_compile->num_pushbacks >= MAX_PUSHBACKS) {
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_INVALID_OPTION),
errmsg("too many tokens %d pushed back, max push back token is: %d",
curr_compile->num_pushbacks,
MAX_PUSHBACKS)));
}
curr_compile->pushback_token[curr_compile->num_pushbacks] = token;
pushback_auxdata[curr_compile->num_pushbacks] = *auxdata;
curr_compile->num_pushbacks++;
}
* Push back a single token to be re-read by next pltsql_yylex() call.
*
* NOTE: this does not cause yylval or yylloc to "back up". Also, it
* is not a good idea to push back a token code other than what you read.
*/
void pltsql_push_back_token(int token)
{
TokenAuxData auxdata;
auxdata.lval = plpgsql_yylval;
auxdata.lloc = plpgsql_yylloc;
auxdata.leng = u_sess->plsql_cxt.curr_compile_context->plpgsql_yyleng;
push_back_token(token, &auxdata);
}
static void location_lineno_init(void)
{
Assert(u_sess->plsql_cxt.curr_compile_context);
PLpgSQL_compile_context* curr_compile = u_sess->plsql_cxt.curr_compile_context;
curr_compile->cur_line_start = curr_compile->scanorig;
curr_compile->cur_line_num = 1;
curr_compile->cur_line_end = strchr(curr_compile->cur_line_start, '\n');
}
* Called before any actual parsing is done
*
* Note: the passed "str" must remain valid until pltsql_scanner_finish().
* Although it is not fed directly to flex, we need the original string
* to cite in error messages.
*/
void pltsql_scanner_init(const char* str)
{
Assert(u_sess->plsql_cxt.curr_compile_context);
PLpgSQL_compile_context* curr_compile = u_sess->plsql_cxt.curr_compile_context;
curr_compile->yyscanner =
scanner_init(str, curr_compile->core_yy, &ReservedPLKeywords, ReservedPLKeywordTokens);
curr_compile->core_yy->isPlpgsqlKeyWord = true;
curr_compile->core_yy->plKeywordValue = &keywordsValue;
* curr_compile->scanorig points to the original string, which unlike the scanner's
* scanbuf won't be modified on-the-fly by flex. Notice that although
* yytext points into scanbuf, we rely on being able to apply locations
* (offsets from string start) to curr_compile->scanorig as well.
*/
curr_compile->scanorig = str;
curr_compile->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
curr_compile->num_pushbacks = 0;
location_lineno_init();
* Note, we do plpgsql SQLs parsing under "PL/pgSQL Function" memory context,
* so it is safe to reset it to NIL in plpgsql execution startup time, even
* in case of execution error-out, it better to help avoid access to in garbage
* pointer
*/
curr_compile->goto_labels = NIL;
}
* Called after parsing is done to clean up after pltsql_scanner_init()
*/
void pltsql_scanner_finish(void)
{
Assert(u_sess->plsql_cxt.curr_compile_context);
PLpgSQL_compile_context* curr_compile = u_sess->plsql_cxt.curr_compile_context;
pgtsql_scanner_finish(curr_compile->yyscanner);
curr_compile->yyscanner = NULL;
curr_compile->scanorig = NULL;
}
* convert self defined token flag to token
*/
static int get_self_defined_tok(int tok_flag)
{
Assert(tok_flag != -1);
switch (tok_flag) {
case PLPGSQL_TOK_REFCURSOR:
return T_REFCURSOR;
case PLPGSQL_TOK_VARRAY:
return T_VARRAY;
case PLPGSQL_TOK_VARRAY_FIRST:
return T_ARRAY_FIRST;
case PLPGSQL_TOK_VARRAY_LAST:
return T_ARRAY_LAST;
case PLPGSQL_TOK_VARRAY_COUNT:
return T_ARRAY_COUNT;
case PLPGSQL_TOK_VARRAY_EXTEND:
return T_ARRAY_EXTEND;
case PLPGSQL_TOK_VARRAY_EXISTS:
return T_ARRAY_EXISTS;
case PLPGSQL_TOK_VARRAY_PRIOR:
return T_ARRAY_PRIOR;
case PLPGSQL_TOK_VARRAY_NEXT:
return T_ARRAY_NEXT;
case PLPGSQL_TOK_VARRAY_DELETE:
return T_ARRAY_DELETE;
case PLPGSQL_TOK_VARRAY_TRIM:
return T_ARRAY_TRIM;
case PLPGSQL_TOK_VARRAY_VAR:
return T_VARRAY_VAR;
case PLPGSQL_TOK_RECORD:
return T_RECORD;
case PLPGSQL_TOK_TABLE:
return T_TABLE;
case PLPGSQL_TOK_TABLE_VAR:
return T_TABLE_VAR;
case PLPGSQL_TOK_PACKAGE_VARIABLE:
return T_PACKAGE_VARIABLE;
case PLPGSQL_TOK_OBJECT_TYPE_VAR_METHOD:
return T_OBJECT_TYPE_VAR_METHOD;
default:
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unknown plpgsql token: %d", tok_flag)));
}
return -1;
}
* parse the cursor attributes and get token.
* if match the cursor attributes, return token,
* and param loc return the location.
* if not match, return -1,
* and push back tokens which were get before.
*/
static int plpgsql_parse_cursor_attribute(int* loc)
{
TokenAuxData aux1;
TokenAuxData aux2;
TokenAuxData aux3;
TokenAuxData aux4;
TokenAuxData aux5;
int tok1;
int tok2;
int tok3;
int tok4;
int tok5;
int token = -1;
PLpgSQL_nsitem* ns = NULL;
bool pkgCursor = false;
if (u_sess->parser_cxt.in_package_function_compile) {
return token;
}
tok1 = internal_yylex(&aux1);
if (tok1 != IDENT && tok1 != PARAM) {
push_back_token(tok1, &aux1);
return token;
}
tok2 = internal_yylex(&aux2);
if (tok2 != '%' && tok2 != '.') {
push_back_token(tok2, &aux2);
push_back_token(tok1, &aux1);
return token;
}
if (tok2 == '.') {
pkgCursor = true;
}
tok3 = internal_yylex(&aux3);
if (tok3 != IDENT) {
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
push_back_token(tok1, &aux1);
return token;
}
if (pkgCursor) {
tok4 = internal_yylex(&aux4);
if (tok4 != '%') {
push_back_token(tok4, &aux4);
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
push_back_token(tok1, &aux1);
return token;
}
tok5 = internal_yylex(&aux5);
if (tok5 != IDENT) {
push_back_token(tok5, &aux5);
push_back_token(tok4, &aux4);
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
push_back_token(tok1, &aux1);
return token;
}
}
if (!pkgCursor) {
if (strncasecmp(aux1.lval.str, "SQL", 3) == 0) {
if (strncasecmp(aux3.lval.str, "ISOPEN", 6) == 0) {
token = T_SQL_ISOPEN;
}
if (strncasecmp(aux3.lval.str, "FOUND", 5) == 0) {
token = T_SQL_FOUND;
}
if (strncasecmp(aux3.lval.str, "NOTFOUND", 8) == 0) {
token = T_SQL_NOTFOUND;
}
if (strncasecmp(aux3.lval.str, "ROWCOUNT", 8) == 0) {
token = T_SQL_ROWCOUNT;
}
if (strncasecmp(aux3.lval.str, "BULK_EXCEPTIONS", strlen("BULK_EXCEPTIONS")) == 0) {
token = T_SQL_BULK_EXCEPTIONS;
}
}
if (strncasecmp(aux1.lval.str, "SQL", 3) != 0) {
if (strncasecmp(aux3.lval.str, "ISOPEN", 6) == 0) {
token = T_CURSOR_ISOPEN;
}
if (strncasecmp(aux3.lval.str, "FOUND", 5) == 0) {
token = T_CURSOR_FOUND;
}
if (strncasecmp(aux3.lval.str, "NOTFOUND", 8) == 0) {
token = T_CURSOR_NOTFOUND;
}
if (strncasecmp(aux3.lval.str, "ROWCOUNT", 8) == 0) {
token = T_CURSOR_ROWCOUNT;
}
}
} else {
if (strncasecmp(aux3.lval.str, "SQL", 3) != 0) {
if (strncasecmp(aux5.lval.str, "ISOPEN", 6) == 0) {
token = T_PACKAGE_CURSOR_ISOPEN;
}
if (strncasecmp(aux5.lval.str, "FOUND", 5) == 0) {
token = T_PACKAGE_CURSOR_FOUND;
}
if (strncasecmp(aux5.lval.str, "NOTFOUND", 8) == 0) {
token = T_PACKAGE_CURSOR_NOTFOUND;
}
if (strncasecmp(aux5.lval.str, "ROWCOUNT", 8) == 0) {
token = T_PACKAGE_CURSOR_ROWCOUNT;
}
}
}
switch (token) {
case T_SQL_ISOPEN:
case T_SQL_FOUND:
case T_SQL_NOTFOUND:
case T_SQL_ROWCOUNT:
*loc = aux1.lloc;
break;
case T_SQL_BULK_EXCEPTIONS:
push_back_token(token, &aux3);
return -1;
break;
case T_CURSOR_ISOPEN:
case T_CURSOR_FOUND:
case T_CURSOR_NOTFOUND:
case T_CURSOR_ROWCOUNT:
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, aux1.lval.str, NULL, NULL, NULL);
if (ns != NULL && ns->itemtype == PLPGSQL_NSTYPE_VAR) {
PLpgSQL_var* var = (PLpgSQL_var*)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[ns->itemno];
if (!(var != NULL && var->datatype && var->datatype->typoid == REFCURSOROID)) {
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("%s isn't a cursor", var ? var->refname : "")));
}
aux1.lval.ival = var->dno;
} else {
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("undefined cursor: %s", aux1.lval.str)));
}
*loc = aux1.lloc;
plpgsql_yylval = aux1.lval;
break;
case T_PACKAGE_CURSOR_ISOPEN:
case T_PACKAGE_CURSOR_FOUND:
case T_PACKAGE_CURSOR_NOTFOUND:
case T_PACKAGE_CURSOR_ROWCOUNT:
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, aux1.lval.str, aux3.lval.str, NULL, NULL);
if (ns == NULL) {
List *idents = list_make2(makeString(aux1.lval.str), makeString(aux3.lval.str));
int dno = plpgsql_pkg_add_unknown_var_to_namespace(idents);
if (dno != -1) {
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false, aux1.lval.str, aux3.lval.str, NULL, NULL);
}
list_free_deep(idents);
}
if (ns != NULL && ns->itemtype == PLPGSQL_NSTYPE_VAR) {
PLpgSQL_var* var = (PLpgSQL_var*)u_sess->plsql_cxt.curr_compile_context->plpgsql_Datums[ns->itemno];
if (!(var != NULL && var->datatype && var->datatype->typoid == REFCURSOROID)) {
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("%s.%s isn't a cursor", aux1.lval.str, aux3.lval.str)));
}
aux1.lval.wdatum.ident = aux1.lval.str;
aux1.lval.wdatum.dno = var->dno;
} else {
ereport(ERROR,
(errmodule(MOD_PLSQL),
errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("undefined cursor: %s.%s", aux1.lval.str, aux3.lval.str)));
}
*loc = aux1.lloc;
plpgsql_yylval = aux1.lval;
break;
default:
if (pkgCursor) {
push_back_token(tok5, &aux5);
push_back_token(tok4, &aux4);
}
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
push_back_token(tok1, &aux1);
break;
}
return token;
}
static int plpgsql_parse_declare(int* loc)
{
TokenAuxData aux1;
int tok1 = -1;
int token = -1;
if (u_sess->parser_cxt.in_package_function_compile) {
return token;
}
tok1 = internal_yylex(&aux1);
if (tok1 == K_DECLARE) {
TokenAuxData aux2;
TokenAuxData aux3;
int tok2 = -1;
int tok3 = -1;
tok2 = internal_yylex(&aux2);
tok3 = internal_yylex(&aux3);
if (tok3 != IDENT || aux3.lval.str == NULL) {
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
push_back_token(tok1, &aux1);
return token;
}
if (strcasecmp(aux3.lval.str, "cursor") == 0) {
u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
token = T_DECLARE_CURSOR;
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
*loc = aux1.lloc;
plpgsql_yylval = aux1.lval;
} else if (strcasecmp(aux3.lval.str, "condition") == 0) {
u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
token = T_DECLARE_CONDITION;
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
*loc = aux1.lloc;
plpgsql_yylval = aux1.lval;
} else if (strcasecmp(aux3.lval.str, "handler") == 0) {
u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
token = T_DECLARE_HANDLER;
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
*loc = aux1.lloc;
plpgsql_yylval = aux1.lval;
} else {
push_back_token(tok3, &aux3);
push_back_token(tok2, &aux2);
push_back_token(tok1, &aux1);
}
} else {
push_back_token(tok1, &aux1);
}
return token;
}
* a convenient method to see if the next two tokens are what we expected
*/
bool pltsql_is_token_match2(int token, int token_next)
{
TokenAuxData aux;
int tok = -1;
int tok_next = -1;
tok = pltsql_yylex();
if (token == tok) {
pltsql_push_back_token(tok);
tok = internal_yylex(&aux);
if (tok >= INT_MAX) {
return false;
}
tok_next = pltsql_yylex();
if (tok_next >= INT_MAX) {
return false;
}
pltsql_push_back_token(tok_next);
push_back_token(tok, &aux);
if (token_next == tok_next) {
return true;
} else {
return false;
}
} else {
pltsql_push_back_token(tok);
return false;
}
}
* a convenient method to see if the next token is what we expected
*/
bool pltsql_is_token_match(int token)
{
int tok = -1;
tok = pltsql_yylex();
if (tok == token) {
pltsql_push_back_token(tok);
return true;
}
pltsql_push_back_token(tok);
return false;
}
* a convenient method to see if the next token is keyword
*/
bool pltsql_is_token_keyword(int token)
{
#define MIN(A, B) ((B) < (A) ? (B) : (A))
#define MAX(A, B) ((B) > (A) ? (B) : (A))
if ((token >= MIN(ReservedPLKeywordTokens[0], UnreservedPLKeywordTokens[0]))
&& (token <= MAX(ReservedPLKeywordTokens[lengthof(ReservedPLKeywordTokens) - 1],
UnreservedPLKeywordTokens[lengthof(UnreservedPLKeywordTokens) - 1]))) {
return true;
} else {
return false;
}
}