From 11447f1163d6fe41169522bf043b503ad2ec5c93 Mon Sep 17 00:00:00 2001 From: Csong Date: Thu, 2 Jul 2026 19:42:01 +0800 Subject: [PATCH] fix: strip trailing semicolon from ALTER statement options A trailing semicolon in the ALTER statement was carried into alterStatementOptions by the greedy (.*$) capture and never trimmed. When those options are spliced into a larger statement (e.g. appending ", ALGORITHM=INSTANT" in generateInstantDDLQuery) the semicolon lands mid-statement. With multiStatements=true on the applier connection this turns one INSTANT DDL into two statements, silently degrading to a native online DDL and, on error, falling back to the full copy. Trim the trailing terminator once in ParseAlterStatement so the options stay a clean fragment. Co-Authored-By: Claude Opus 4.8 --- go/sql/parser.go | 5 +++++ go/sql/parser_test.go | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/go/sql/parser.go b/go/sql/parser.go index 72e44ea62..dfe02b815 100644 --- a/go/sql/parser.go +++ b/go/sql/parser.go @@ -150,6 +150,11 @@ func (atp *AlterTableParser) ParseAlterStatement(alterStatement string) (err err break } } + // Strip any trailing statement terminator. A trailing semicolon would + // otherwise be carried into alterStatementOptions and, when the options are + // spliced into a larger statement (e.g. appending ", ALGORITHM=INSTANT"), + // break it into two statements under multiStatements=true. + atp.alterStatementOptions = strings.TrimRight(atp.alterStatementOptions, "; \t\r\n") for _, alterToken := range atp.tokenizeAlterStatement(atp.alterStatementOptions) { alterToken = atp.sanitizeQuotesFromAlterStatement(alterToken) atp.parseAlterToken(alterToken) diff --git a/go/sql/parser_test.go b/go/sql/parser_test.go index b69742bdd..c2cabbe9e 100644 --- a/go/sql/parser_test.go +++ b/go/sql/parser_test.go @@ -26,6 +26,27 @@ func TestParseAlterStatement(t *testing.T) { require.False(t, parser.IsAutoIncrementDefined()) } +func TestParseAlterStatementTrailingSemicolon(t *testing.T) { + statements := []string{ + "add column t int;", + "add column t int ;", + "add column t int; \t\r\n", + "ADD KEY idx_username (`username`);", + } + expected := []string{ + "add column t int", + "add column t int", + "add column t int", + "ADD KEY idx_username (`username`)", + } + for i, statement := range statements { + parser := NewAlterTableParser() + err := parser.ParseAlterStatement(statement) + require.NoError(t, err) + require.Equal(t, expected[i], parser.alterStatementOptions) + } +} + func TestParseAlterStatementrivialRename(t *testing.T) { statement := "add column t int, change ts ts timestamp, engine=innodb" parser := NewAlterTableParser()