diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y index eb283a8632..802c0ad0a6 100644 --- a/src/backend/replication/repl_gram.y +++ b/src/backend/replication/repl_gram.y @@ -25,8 +25,6 @@ /* Result of the parsing is returned here */ Node *replication_parse_result; -static SQLCmd *make_sqlcmd(void); - /* * Bison doesn't allocate anything that needs to live across parser calls, @@ -59,7 +57,6 @@ static SQLCmd *make_sqlcmd(void); %token SCONST IDENT %token UCONST %token RECPTR -%token T_WORD /* Keyword tokens. */ %token K_BASE_BACKUP @@ -93,7 +90,7 @@ static SQLCmd *make_sqlcmd(void); %type command %type base_backup start_replication start_logical_replication create_replication_slot drop_replication_slot identify_system - timeline_history show sql_cmd + timeline_history show %type base_backup_opt_list %type base_backup_opt %type opt_timeline @@ -126,7 +123,6 @@ command: | drop_replication_slot | timeline_history | show - | sql_cmd ; /* @@ -413,25 +409,6 @@ plugin_opt_arg: | /* EMPTY */ { $$ = NULL; } ; -sql_cmd: - IDENT { $$ = (Node *) make_sqlcmd(); } - ; %% -static SQLCmd * -make_sqlcmd(void) -{ - SQLCmd *cmd = makeNode(SQLCmd); - int tok; - - /* Just move lexer to the end of command. */ - for (;;) - { - tok = yylex(); - if (tok == ';' || tok == 0) - break; - } - return cmd; -} - #include "repl_scanner.c" diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l index 02d025cd14..8a075e2d92 100644 --- a/src/backend/replication/repl_scanner.l +++ b/src/backend/replication/repl_scanner.l @@ -31,6 +31,10 @@ fprintf_to_ereport(const char *fmt, const char *msg) /* Handle to the buffer that the lexer uses internally */ static YY_BUFFER_STATE scanbufhandle; +/* Pushed-back token (we only handle one) */ +static int repl_pushed_back_token; + +/* Work area for collecting literals */ static StringInfoData litbuf; static void startlit(void); @@ -51,7 +55,18 @@ static void addlitchar(unsigned char ychar); %option warn %option prefix="replication_yy" -%x xq xd +/* + * Exclusive states: + * delimited identifiers (double-quoted identifiers) + * standard single-quoted strings + */ +%x xd +%x xq + +space [ \t\n\r\f] + +quote ' +quotestop {quote} /* Extended quote * xqdouble implements embedded quote, '''' @@ -69,11 +84,8 @@ xdstop {dquote} xddouble {dquote}{dquote} xdinside [^"]+ -digit [0-9]+ -hexdigit [0-9A-Za-z]+ - -quote ' -quotestop {quote} +digit [0-9] +hexdigit [0-9A-Fa-f] ident_start [A-Za-z\200-\377_] ident_cont [A-Za-z\200-\377_0-9\$] @@ -82,6 +94,19 @@ identifier {ident_start}{ident_cont}* %% +%{ + /* This code is inserted at the start of replication_yylex() */ + + /* If we have a pushed-back token, return that. */ + if (repl_pushed_back_token) + { + int result = repl_pushed_back_token; + + repl_pushed_back_token = 0; + return result; + } +%} + BASE_BACKUP { return K_BASE_BACKUP; } FAST { return K_FAST; } IDENTIFY_SYSTEM { return K_IDENTIFY_SYSTEM; } @@ -110,14 +135,7 @@ WAIT { return K_WAIT; } MANIFEST { return K_MANIFEST; } MANIFEST_CHECKSUMS { return K_MANIFEST_CHECKSUMS; } -"," { return ','; } -";" { return ';'; } -"(" { return '('; } -")" { return ')'; } - -[\n] ; -[\t] ; -" " ; +{space}+ { /* do nothing */ } {digit}+ { yylval.uintval = strtoul(yytext, NULL, 10); @@ -179,6 +197,11 @@ MANIFEST_CHECKSUMS { return K_MANIFEST_CHECKSUMS; } return IDENT; } +. { + /* Any char not recognized above is returned as itself */ + return yytext[0]; + } + <> { yyerror("unterminated quoted string"); } @@ -186,9 +209,6 @@ MANIFEST_CHECKSUMS { return K_MANIFEST_CHECKSUMS; } yyterminate(); } -. { - return T_WORD; - } %% /* LCOV_EXCL_STOP */ @@ -248,6 +268,7 @@ replication_scanner_init(const char *str) /* Make sure we start in proper state */ BEGIN(INITIAL); + repl_pushed_back_token = 0; } void @@ -256,3 +277,34 @@ replication_scanner_finish(void) yy_delete_buffer(scanbufhandle); scanbufhandle = NULL; } + +/* + * Check to see if the first token of a command is a WalSender keyword. + * + * To keep repl_scanner.l minimal, we don't ask it to know every construct + * that the core lexer knows. Therefore, we daren't lex more than the + * first token of a general SQL command. That will usually look like an + * IDENT token here, although some other cases are possible. + */ +bool +replication_scanner_is_replication_command(void) +{ + int first_token = replication_yylex(); + + switch (first_token) + { + case K_IDENTIFY_SYSTEM: + case K_BASE_BACKUP: + case K_START_REPLICATION: + case K_CREATE_REPLICATION_SLOT: + case K_DROP_REPLICATION_SLOT: + case K_TIMELINE_HISTORY: + case K_SHOW: + /* Yes; push back the first token so we can parse later. */ + repl_pushed_back_token = first_token; + return true; + default: + /* Nope; we don't bother to push back the token. */ + return false; + } +} diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 3224536356..3b245c619f 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1520,7 +1520,8 @@ exec_replication_command(const char *cmd_string) */ if (MyWalSnd->state == WALSNDSTATE_STOPPING) ereport(ERROR, - (errmsg("cannot execute new commands while WAL sender is in stopping mode"))); + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot execute new commands while WAL sender is in stopping mode"))); /* * CREATE_REPLICATION_SLOT ... LOGICAL exports a snapshot until the next @@ -1531,7 +1532,7 @@ exec_replication_command(const char *cmd_string) CHECK_FOR_INTERRUPTS(); /* - * Parse the command. + * Prepare to parse and execute the command. */ cmd_context = AllocSetContextCreate(CurrentMemoryContext, "Replication command context", @@ -1539,6 +1540,31 @@ exec_replication_command(const char *cmd_string) old_context = MemoryContextSwitchTo(cmd_context); replication_scanner_init(cmd_string); + + /* + * Is it a WalSender command? + */ + if (!replication_scanner_is_replication_command()) + { + /* Nope; clean up and get out. */ + replication_scanner_finish(); + + MemoryContextSwitchTo(old_context); + MemoryContextDelete(cmd_context); + + /* XXX this is a pretty random place to make this check */ + if (MyDatabaseId == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot execute SQL commands in WAL sender for physical replication"))); + + /* Tell the caller that this wasn't a WalSender command. */ + return false; + } + + /* + * Looks like a WalSender command, so parse it. + */ parse_rc = replication_yyparse(); if (parse_rc != 0) ereport(ERROR, @@ -1549,23 +1575,6 @@ exec_replication_command(const char *cmd_string) cmd_node = replication_parse_result; - /* - * If it's a SQL command, just clean up our mess and return false; the - * caller will take care of executing it. - */ - if (IsA(cmd_node, SQLCmd)) - { - if (MyDatabaseId == InvalidOid) - ereport(ERROR, - (errmsg("cannot execute SQL commands in WAL sender for physical replication"))); - - MemoryContextSwitchTo(old_context); - MemoryContextDelete(cmd_context); - - /* Tell the caller that this wasn't a WalSender command. */ - return false; - } - /* * Report query to various monitoring facilities. For this purpose, we * report replication commands just like SQL commands. diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h index 68571072e7..07abf26ff3 100644 --- a/src/include/replication/walsender_private.h +++ b/src/include/replication/walsender_private.h @@ -121,6 +121,7 @@ extern int replication_yylex(void); extern void replication_yyerror(const char *str) pg_attribute_noreturn(); extern void replication_scanner_init(const char *query_string); extern void replication_scanner_finish(void); +extern bool replication_scanner_is_replication_command(void); extern Node *replication_parse_result;