int *endtoken);
static PLpgSQL_expr *read_sql_stmt(void);
static PLpgSQL_type *read_datatype(int tok);
-static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location);
+static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location,
+ PLword *word);
static PLpgSQL_stmt_fetch *read_fetch_direction(void);
static void complete_direction(PLpgSQL_stmt_fetch *fetch,
bool *check_FROM);
*/
stmt_execsql : K_IMPORT
{
- $$ = make_execsql_stmt(K_IMPORT, @1);
+ $$ = make_execsql_stmt(K_IMPORT, @1, NULL);
}
| K_INSERT
{
- $$ = make_execsql_stmt(K_INSERT, @1);
+ $$ = make_execsql_stmt(K_INSERT, @1, NULL);
}
| K_MERGE
{
- $$ = make_execsql_stmt(K_MERGE, @1);
+ $$ = make_execsql_stmt(K_MERGE, @1, NULL);
}
| T_WORD
{
if (tok == '=' || tok == COLON_EQUALS ||
tok == '[' || tok == '.')
word_is_not_variable(&($1), @1);
- $$ = make_execsql_stmt(T_WORD, @1);
+ $$ = make_execsql_stmt(T_WORD, @1, &($1));
}
| T_CWORD
{
if (tok == '=' || tok == COLON_EQUALS ||
tok == '[' || tok == '.')
cword_is_not_variable(&($1), @1);
- $$ = make_execsql_stmt(T_CWORD, @1);
+ $$ = make_execsql_stmt(T_CWORD, @1, NULL);
}
;
return result;
}
+/*
+ * Read a generic SQL statement. We have already read its first token;
+ * firsttoken is that token's code and location its starting location.
+ * If firsttoken == T_WORD, pass its yylval value as "word", else pass NULL.
+ */
static PLpgSQL_stmt *
-make_execsql_stmt(int firsttoken, int location)
+make_execsql_stmt(int firsttoken, int location, PLword *word)
{
StringInfoData ds;
IdentifierLookup save_IdentifierLookup;
bool have_strict = false;
int into_start_loc = -1;
int into_end_loc = -1;
+ int paren_depth = 0;
+ int begin_depth = 0;
+ bool in_routine_definition = false;
+ int token_count = 0;
+ char tokens[4]; /* records the first few tokens */
initStringInfo(&ds);
+ memset(tokens, 0, sizeof(tokens));
+
/* special lookup mode for identifiers within the SQL text */
save_IdentifierLookup = plpgsql_IdentifierLookup;
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().
*
+ * The end of the statement is defined by a semicolon ... except that
+ * semicolons within parentheses or BEGIN/END blocks don't terminate a
+ * statement. We follow psql's lead in not recognizing BEGIN/END except
+ * after CREATE [OR REPLACE] {FUNCTION|PROCEDURE}. END can also appear
+ * within a CASE construct, so we treat CASE/END like BEGIN/END.
+ *
* 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:
* break this logic again ... beware!
*/
tok = firsttoken;
+ if (tok == T_WORD && strcmp(word->ident, "create") == 0)
+ tokens[token_count] = 'c';
+ token_count++;
+
for (;;)
{
prev_tok = tok;
tok = yylex();
if (have_into && into_end_loc < 0)
into_end_loc = yylloc; /* token after the INTO part */
- if (tok == ';')
+ /* Detect CREATE [OR REPLACE] {FUNCTION|PROCEDURE} */
+ if (tokens[0] == 'c' && token_count < sizeof(tokens))
+ {
+ if (tok == K_OR)
+ tokens[token_count] = 'o';
+ else if (tok == T_WORD &&
+ strcmp(yylval.word.ident, "replace") == 0)
+ tokens[token_count] = 'r';
+ else if (tok == T_WORD &&
+ strcmp(yylval.word.ident, "function") == 0)
+ tokens[token_count] = 'f';
+ else if (tok == T_WORD &&
+ strcmp(yylval.word.ident, "procedure") == 0)
+ tokens[token_count] = 'f'; /* treat same as "function" */
+ if (tokens[1] == 'f' ||
+ (tokens[1] == 'o' && tokens[2] == 'r' && tokens[3] == 'f'))
+ in_routine_definition = true;
+ token_count++;
+ }
+ /* Track paren nesting (needed for CREATE RULE syntax) */
+ if (tok == '(')
+ paren_depth++;
+ else if (tok == ')' && paren_depth > 0)
+ paren_depth--;
+ /* We need track BEGIN/END nesting only in a routine definition */
+ if (in_routine_definition && paren_depth == 0)
+ {
+ if (tok == K_BEGIN || tok == K_CASE)
+ begin_depth++;
+ else if (tok == K_END && begin_depth > 0)
+ begin_depth--;
+ }
+ /* Command-ending semicolon? */
+ if (tok == ';' && paren_depth == 0 && begin_depth == 0)
break;
if (tok == 0)
yyerror("unexpected end of function definition");