Skip to content

Commit 029c5ac

Browse files
committed
psql: Refine lexing of BEGIN...END blocks in CREATE FUNCTION statements
Only track BEGIN...END blocks if they are in a CREATE [OR REPLACE] {FUNCTION|PROCEDURE} statement. Ignore if in parentheses. Reviewed-by: Laurenz Albe <laurenz.albe@cybertec.at> Discussion: https://www.postgresql.org/message-id/cee01d26fe55bc086b3bcf10bfe4e8d450e2f608.camel@cybertec.at
1 parent 25593d7 commit 029c5ac

File tree

2 files changed

+52
-9
lines changed

2 files changed

+52
-9
lines changed

src/fe_utils/psqlscan.l

+45-8
Original file line numberDiff line numberDiff line change
@@ -870,18 +870,55 @@ other .
870870

871871

872872
{identifier} {
873-
cur_state->identifier_count++;
874-
if (pg_strcasecmp(yytext, "begin") == 0
875-
|| pg_strcasecmp(yytext, "case") == 0)
873+
/*
874+
* We need to track if we are inside a BEGIN .. END block
875+
* in a function definition, so that semicolons contained
876+
* therein don't terminate the whole statement. Short of
877+
* writing a full parser here, the following heuristic
878+
* should work. First, we track whether the beginning of
879+
* the statement matches CREATE [OR REPLACE]
880+
* {FUNCTION|PROCEDURE}
881+
*/
882+
883+
if (cur_state->identifier_count == 0)
884+
memset(cur_state->identifiers, 0, sizeof(cur_state->identifiers));
885+
886+
if (pg_strcasecmp(yytext, "create") == 0 ||
887+
pg_strcasecmp(yytext, "function") == 0 ||
888+
pg_strcasecmp(yytext, "procedure") == 0 ||
889+
pg_strcasecmp(yytext, "or") == 0 ||
890+
pg_strcasecmp(yytext, "replace") == 0)
876891
{
877-
if (cur_state->identifier_count > 1)
878-
cur_state->begin_depth++;
892+
if (cur_state->identifier_count < sizeof(cur_state->identifiers))
893+
cur_state->identifiers[cur_state->identifier_count] = pg_tolower((unsigned char) yytext[0]);
879894
}
880-
else if (pg_strcasecmp(yytext, "end") == 0)
895+
896+
cur_state->identifier_count++;
897+
898+
if (cur_state->identifiers[0] == 'c' &&
899+
(cur_state->identifiers[1] == 'f' || cur_state->identifiers[1] == 'p' ||
900+
(cur_state->identifiers[1] == 'o' && cur_state->identifiers[2] == 'r' &&
901+
(cur_state->identifiers[3] == 'f' || cur_state->identifiers[3] == 'p'))) &&
902+
cur_state->paren_depth == 0)
881903
{
882-
if (cur_state->begin_depth > 0)
883-
cur_state->begin_depth--;
904+
if (pg_strcasecmp(yytext, "begin") == 0)
905+
cur_state->begin_depth++;
906+
else if (pg_strcasecmp(yytext, "case") == 0)
907+
{
908+
/*
909+
* CASE also ends with END. We only need to track
910+
* this if we are already inside a BEGIN.
911+
*/
912+
if (cur_state->begin_depth >= 1)
913+
cur_state->begin_depth++;
914+
}
915+
else if (pg_strcasecmp(yytext, "end") == 0)
916+
{
917+
if (cur_state->begin_depth > 0)
918+
cur_state->begin_depth--;
919+
}
884920
}
921+
885922
ECHO;
886923
}
887924

src/include/fe_utils/psqlscan_int.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,14 @@ typedef struct PsqlScanStateData
114114
int paren_depth; /* depth of nesting in parentheses */
115115
int xcdepth; /* depth of nesting in slash-star comments */
116116
char *dolqstart; /* current $foo$ quote start string */
117+
118+
/*
119+
* State to track boundaries of BEGIN ... END blocks in function
120+
* definitions, so that semicolons do not send query too early.
121+
*/
117122
int identifier_count; /* identifiers since start of statement */
118-
int begin_depth; /* depth of begin/end routine body blocks */
123+
char identifiers[4]; /* records the first few identifiers */
124+
int begin_depth; /* depth of begin/end pairs */
119125

120126
/*
121127
* Callback functions provided by the program making use of the lexer,

0 commit comments

Comments
 (0)